mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-08 02:42:58 +00:00
Compare commits
131 Commits
block-posi
...
overloaded
Author | SHA1 | Date | |
---|---|---|---|
016614e714 | |||
9e7e76a609 | |||
b1e4e44f77 | |||
f4f31b654b | |||
799dfecdd9 | |||
1415e2492a | |||
51cf6817b1 | |||
fd04894a7b | |||
d2d6a59c72 | |||
788ee9a008 | |||
34f801ee3c | |||
d96ef21c4d | |||
246c1776df | |||
03e4b53ac4 | |||
91ac64783f | |||
9402a20ee3 | |||
2670e81668 | |||
e29aa2f337 | |||
5ef89200c6 | |||
9b3b45258a | |||
ca4debbf08 | |||
39e69276a1 | |||
94797e3afa | |||
03f98ee0a9 | |||
70368ea653 | |||
9d6a0cc738 | |||
21ccd90147 | |||
0a9a45a126 | |||
88937f1785 | |||
07987890a0 | |||
e5a783cb9e | |||
70fb9bbdfd | |||
f60120a75e | |||
fc86d3a44e | |||
393d7f72a9 | |||
b625fee94b | |||
6b606dca95 | |||
406ddf3e53 | |||
ec6077776a | |||
f7b5cd7ff3 | |||
3453ff03fd | |||
04e63172c3 | |||
51cb1875bb | |||
0b60a47cde | |||
d1066d0199 | |||
f349ce75e4 | |||
b3f15435cc | |||
847ae26cad | |||
d42ec06647 | |||
5e0f03dff0 | |||
4a83920db9 | |||
0a16daa619 | |||
e34f34f9f4 | |||
e8c4b743b5 | |||
689a7996b9 | |||
794641c0f8 | |||
9633b7d8a7 | |||
d25ec58a6f | |||
d69a887b0d | |||
38441a6ba3 | |||
47cb04f6a6 | |||
b1c7fc017a | |||
cd59e272bc | |||
7b1b35ab1f | |||
28d31c97f8 | |||
a17512de93 | |||
601be3fb33 | |||
2e32c50670 | |||
d1fa6edc50 | |||
a1ba8bc3da | |||
73edb8799d | |||
9592f066f3 | |||
db9ba83001 | |||
1b2d2a3fe1 | |||
84ec8b7abe | |||
357dfb5c7e | |||
0358b7dce4 | |||
97c5902ae2 | |||
9a130bce32 | |||
20849d6268 | |||
b6bd3ef30c | |||
c5a1c15389 | |||
e30ae487dc | |||
59f6c85105 | |||
90f0b85d2e | |||
8ee70b209e | |||
5c905d9a95 | |||
8b23231537 | |||
8e039f2711 | |||
4a4572131f | |||
3da0b82b86 | |||
da62eb9f33 | |||
09c434983b | |||
fbaa125d0c | |||
6ad9dde43d | |||
d634a5fa3d | |||
8cea4c13c4 | |||
dc2e82df7f | |||
debf8d18fc | |||
81e3730b99 | |||
8a5eb71432 | |||
306623e890 | |||
ada3acdba4 | |||
6a1d80e021 | |||
1ff0988e75 | |||
506cfe0362 | |||
fea17fa4a9 | |||
aee358d329 | |||
80899ea72c | |||
42f90e94ff | |||
8f536e6f21 | |||
45482e868d | |||
742aa46b88 | |||
cf1b360a62 | |||
0aa6cde259 | |||
8f8fe948c1 | |||
b10caf7437 | |||
de66d84d29 | |||
636f562bcf | |||
42094e6768 | |||
b341078765 | |||
f7687af337 | |||
ba93665fe7 | |||
6817215683 | |||
67b9d6222d | |||
6f197bc1bb | |||
bba525da02 | |||
ad6d34f1a6 | |||
a8eaa43bc8 | |||
fe7c282052 | |||
45917d495c |
19
.github/ISSUE_TEMPLATE/api-change-request.md
vendored
19
.github/ISSUE_TEMPLATE/api-change-request.md
vendored
@ -1,19 +0,0 @@
|
||||
---
|
||||
name: API change request
|
||||
about: Suggest a change, addition or removal to the plugin API
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--- Describe the problem you want to solve -->
|
||||
## Problem description
|
||||
|
||||
|
||||
<!--- Describe what changes you want to make to solve this problem -->
|
||||
## Proposed solution
|
||||
|
||||
|
||||
<!--- (optional) describe alternative methods you've explored to achieve your goal -->
|
||||
## Alternative solutions that don't require API changes
|
84
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
84
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@ -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
|
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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
|
||||
<!-- try the `version` command | LATEST IS NOT A VALID VERSION -->
|
||||
* PocketMine-MP:
|
||||
* PHP:
|
||||
* Using JIT: yes/no (delete as appropriate) <!-- look for the giant yellow warning in the log that says you're using JIT -->
|
||||
* Server OS:
|
||||
* Game version: Android/iOS/Win10/Xbox/PS4/Switch (delete as appropriate)
|
||||
|
||||
### Plugins
|
||||
<!--- use the `plugins` command and paste the output below -->
|
||||
|
||||
- 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
|
||||
<!--- Submit crashdumps at https://crash.pmmp.io and paste a link -->
|
||||
<!--- Use gist or anything else to add other files and add links here -->
|
16
.github/ISSUE_TEMPLATE/crash.md
vendored
16
.github/ISSUE_TEMPLATE/crash.md
vendored
@ -1,16 +0,0 @@
|
||||
---
|
||||
name: Crash
|
||||
about: Report a crash in PocketMine-MP (not plugins)
|
||||
title: Server crashed
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--- submit crashdump files to https://crash.pmmp.io -->
|
||||
<!--- or, copy the data between ===BEGIN CRASH DUMP=== and ===END CRASH DUMP and paste it on a site like https://pastebin.com -->
|
||||
<!--- DON'T JUST PASTE the crashdump into an issue -->
|
||||
Link to crashdump:
|
||||
|
||||
<!--- write additional information about the crash to help us find the problem -->
|
||||
### Additional comments (optional)
|
25
.github/ISSUE_TEMPLATE/crash.yml
vendored
Normal file
25
.github/ISSUE_TEMPLATE/crash.yml
vendored
Normal file
@ -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
|
19
.github/ISSUE_TEMPLATE/feature-proposal.yml
vendored
Normal file
19
.github/ISSUE_TEMPLATE/feature-proposal.yml
vendored
Normal file
@ -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"
|
8
.github/workflows/build-docker-image.yml
vendored
8
.github/workflows/build-docker-image.yml
vendored
@ -53,7 +53,7 @@ jobs:
|
||||
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build image for tag
|
||||
uses: docker/build-push-action@v6.10.0
|
||||
uses: docker/build-push-action@v6.13.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
- name: Build image for major tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.10.0
|
||||
uses: docker/build-push-action@v6.13.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
- name: Build image for minor tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.10.0
|
||||
uses: docker/build-push-action@v6.13.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -92,7 +92,7 @@ jobs:
|
||||
|
||||
- name: Build image for latest tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.10.0
|
||||
uses: docker/build-push-action@v6.13.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
|
2
.github/workflows/discord-release-notify.yml
vendored
2
.github/workflows/discord-release-notify.yml
vendored
@ -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
|
||||
|
||||
|
65
.github/workflows/draft-release-from-pr.yml
vendored
65
.github/workflows/draft-release-from-pr.yml
vendored
@ -1,65 +0,0 @@
|
||||
name: Draft release from PR
|
||||
|
||||
on:
|
||||
#presume that pull_request_target is safe at this point, since the PR was approved and merged
|
||||
#we need write access to prepare the release & create comments
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- stable
|
||||
- minor-next
|
||||
- major-next
|
||||
- "legacy/*"
|
||||
paths:
|
||||
- "src/VersionInfo.php"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check release
|
||||
uses: ./.github/workflows/draft-release-pr-check.yml
|
||||
|
||||
draft:
|
||||
name: Create GitHub draft release
|
||||
needs: [check]
|
||||
if: needs.check.outputs.valid == 'true'
|
||||
|
||||
uses: ./.github/workflows/draft-release.yml
|
||||
|
||||
post-draft-url-comment:
|
||||
name: Post draft release URL as comment
|
||||
needs: [draft]
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Post draft release URL on PR
|
||||
uses: thollander/actions-comment-pull-request@v3
|
||||
with:
|
||||
message: "[Draft release ${{ needs.draft.outputs.version }}](${{ needs.draft.outputs.draft-url }}) has been created for commit ${{ github.sha }}. Please review and publish it."
|
||||
|
||||
trigger-post-release-workflow:
|
||||
name: Trigger post-release RestrictedActions workflow
|
||||
# Not sure if needs is actually needed here
|
||||
needs: [check]
|
||||
if: needs.check.outputs.valid == 'true'
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Generate access token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
|
||||
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: RestrictedActions
|
||||
|
||||
- name: Dispatch post-release restricted action
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
repository: ${{ github.repository_owner }}/RestrictedActions
|
||||
event-type: pocketmine_mp_post_release
|
||||
client-payload: '{"branch": "${{ github.ref }}"}'
|
13
.github/workflows/draft-release-from-tag.yml
vendored
13
.github/workflows/draft-release-from-tag.yml
vendored
@ -1,13 +0,0 @@
|
||||
#Allows creating a release by pushing a tag
|
||||
#This might be useful for retroactive releases
|
||||
name: Draft release from git tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: "*"
|
||||
|
||||
jobs:
|
||||
draft:
|
||||
name: Create GitHub draft release
|
||||
if: "startsWith(github.event.head_commit.message, 'Release ')"
|
||||
uses: ./.github/workflows/draft-release.yml
|
2
.github/workflows/draft-release-pr-check.yml
vendored
2
.github/workflows/draft-release-pr-check.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.31.1
|
||||
uses: shivammathur/setup-php@2.32.0
|
||||
with:
|
||||
php-version: 8.2
|
||||
|
||||
|
122
.github/workflows/draft-release.yml
vendored
122
.github/workflows/draft-release.yml
vendored
@ -1,28 +1,85 @@
|
||||
name: Draft release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
draft-url:
|
||||
description: 'The URL of the draft release'
|
||||
value: ${{ jobs.draft.outputs.draft-url }}
|
||||
version:
|
||||
description: 'PocketMine-MP version'
|
||||
value: ${{ jobs.draft.outputs.version }}
|
||||
#presume that pull_request_target is safe at this point, since the PR was approved and merged
|
||||
#we need write access to prepare the release & create comments
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- stable
|
||||
- minor-next
|
||||
- major-next
|
||||
- "legacy/*"
|
||||
paths:
|
||||
- "src/VersionInfo.php"
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
env:
|
||||
PHP_VERSION: "8.2"
|
||||
|
||||
jobs:
|
||||
draft:
|
||||
name: Create GitHub draft release
|
||||
|
||||
skip:
|
||||
name: Check whether to ignore this tag
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version: [8.2]
|
||||
|
||||
outputs:
|
||||
draft-url: ${{ steps.create-draft.outputs.html_url }}
|
||||
version: ${{ steps.get-pm-version.outputs.PM_VERSION }}
|
||||
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
|
||||
needs: [check]
|
||||
if: needs.check.outputs.valid == 'true' || github.ref_type == 'tag' #ignore validity check for tags
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -30,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
|
||||
@ -50,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
|
||||
|
||||
@ -63,23 +120,31 @@ jobs:
|
||||
- name: Get PocketMine-MP release version
|
||||
id: get-pm-version
|
||||
run: |
|
||||
echo PM_VERSION=$(php build/dump-version-info.php base_version) >> $GITHUB_OUTPUT
|
||||
PM_VERSION=$(php build/dump-version-info.php base_version)
|
||||
echo PM_VERSION=$PM_VERSION >> $GITHUB_OUTPUT
|
||||
echo PM_MAJOR=$(php build/dump-version-info.php major_version) >> $GITHUB_OUTPUT
|
||||
echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT
|
||||
echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT
|
||||
echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT
|
||||
echo PRERELEASE=$(php build/dump-version-info.php prerelease) >> $GITHUB_OUTPUT
|
||||
|
||||
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
|
||||
tag="$(echo "${{ github.ref }}" | cut -d/ -f3-)"
|
||||
else
|
||||
tag="$PM_VERSION"
|
||||
fi
|
||||
echo TAG_NAME=$tag >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate PHP binary download URL
|
||||
id: php-binary-url
|
||||
run: |
|
||||
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT
|
||||
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ env.PHP_VERSION }}-latest" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate build info
|
||||
run: |
|
||||
php build/generate-build-info-json.php \
|
||||
${{ github.sha }} \
|
||||
${{ steps.get-pm-version.outputs.PM_VERSION }} \
|
||||
${{ steps.get-pm-version.outputs.TAG_NAME }} \
|
||||
${{ github.repository }} \
|
||||
${{ steps.build-number.outputs.BUILD_NUMBER }} \
|
||||
${{ github.run_id }} \
|
||||
@ -100,7 +165,7 @@ jobs:
|
||||
${{ github.workspace }}/core-permissions.rst
|
||||
|
||||
- name: Create draft release
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
uses: ncipollo/release-action@v1.15.0
|
||||
id: create-draft
|
||||
with:
|
||||
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst
|
||||
@ -108,12 +173,17 @@ jobs:
|
||||
draft: true
|
||||
prerelease: ${{ steps.get-pm-version.outputs.PRERELEASE }}
|
||||
name: PocketMine-MP ${{ steps.get-pm-version.outputs.PM_VERSION }}
|
||||
tag: ${{ steps.get-pm-version.outputs.PM_VERSION }}
|
||||
tag: ${{ steps.get-pm-version.outputs.TAG_NAME }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
skipIfReleaseExists: true #for release PRs, tags will be created on release publish and trigger the tag release workflow - don't create a second draft
|
||||
body: |
|
||||
**For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}**
|
||||
|
||||
Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.CHANGELOG_FILE_NAME }}#${{ steps.get-pm-version.outputs.CHANGELOG_MD_HEADER }}) for details.
|
||||
|
||||
:information_source: Download the recommended PHP binary [here](${{ steps.php-binary-url.outputs.PHP_BINARY_URL }}).
|
||||
|
||||
- name: Post draft release URL on PR
|
||||
if: github.event_name == 'pull_request_target'
|
||||
uses: thollander/actions-comment-pull-request@v3
|
||||
with:
|
||||
message: "[Draft release ${{ steps.get-pm-version.outputs.PM_VERSION }}](${{ steps.create-draft.outputs.html_url }}) has been created for commit ${{ github.sha }}. Please review and publish it."
|
||||
|
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -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
|
||||
|
@ -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;
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\build\update_registry_annotations;
|
||||
|
||||
use pocketmine\utils\OverloadedRegistryMember;
|
||||
use pocketmine\utils\Utils;
|
||||
use function basename;
|
||||
use function class_exists;
|
||||
@ -34,6 +35,7 @@ use function fwrite;
|
||||
use function implode;
|
||||
use function is_dir;
|
||||
use function ksort;
|
||||
use function lcfirst;
|
||||
use function mb_strtoupper;
|
||||
use function preg_match;
|
||||
use function sprintf;
|
||||
@ -47,11 +49,19 @@ if(count($argv) !== 2){
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param \ReflectionClass<*> $class
|
||||
*/
|
||||
function makeTypehint(string $namespaceName, \ReflectionClass $class) : string{
|
||||
return $class->getNamespaceName() === $namespaceName ? $class->getShortName() : '\\' . $class->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object[] $members
|
||||
* @phpstan-param array<string, object> $members
|
||||
* @phpstan-param array<string, OverloadedRegistryMember> $overloadedMembers
|
||||
*/
|
||||
function generateMethodAnnotations(string $namespaceName, array $members) : string{
|
||||
function generateMethodAnnotations(string $namespaceName, array $members, array $overloadedMembers) : string{
|
||||
$selfName = basename(__FILE__);
|
||||
$lines = ["/**"];
|
||||
$lines[] = " * This doc-block is generated automatically, do not modify it manually.";
|
||||
@ -69,14 +79,20 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri
|
||||
}
|
||||
if($reflect === false){
|
||||
$typehint = "object";
|
||||
}elseif($reflect->getNamespaceName() === $namespaceName){
|
||||
$typehint = $reflect->getShortName();
|
||||
}else{
|
||||
$typehint = '\\' . $reflect->getName();
|
||||
$typehint = makeTypehint($namespaceName, $reflect);
|
||||
}
|
||||
$accessor = mb_strtoupper($name);
|
||||
$memberLines[$accessor] = sprintf($lineTmpl, $accessor, $typehint);
|
||||
}
|
||||
foreach(Utils::stringifyKeys($overloadedMembers) as $baseName => $member){
|
||||
$accessor = mb_strtoupper($baseName);
|
||||
$returnTypehint = makeTypehint($namespaceName, new \ReflectionClass($member->memberClass));
|
||||
$enumReflect = new \ReflectionClass($member->enumClass);
|
||||
$paramTypehint = makeTypehint($namespaceName, $enumReflect);
|
||||
|
||||
$memberLines[] = sprintf(" * @method static %s %s(%s \$%s)", $returnTypehint, $accessor, $paramTypehint, lcfirst($enumReflect->getShortName()));
|
||||
}
|
||||
ksort($memberLines, SORT_STRING);
|
||||
|
||||
foreach($memberLines as $line){
|
||||
@ -107,7 +123,7 @@ function processFile(string $file) : void{
|
||||
}
|
||||
echo "Found registry in $file\n";
|
||||
|
||||
$replacement = generateMethodAnnotations($matches[1], $className::getAll());
|
||||
$replacement = generateMethodAnnotations($matches[1], $className::getAll(), $className::getAllOverloaded());
|
||||
|
||||
$newContents = str_replace($docComment, $replacement, $contents);
|
||||
if($newContents !== $contents){
|
||||
|
@ -1,174 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\build\make_release;
|
||||
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\utils\VersionString;
|
||||
use pocketmine\VersionInfo;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function dirname;
|
||||
use function fgets;
|
||||
use function file_put_contents;
|
||||
use function fwrite;
|
||||
use function getopt;
|
||||
use function is_string;
|
||||
use function max;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function str_pad;
|
||||
use function strlen;
|
||||
use function strtolower;
|
||||
use function system;
|
||||
use const STDERR;
|
||||
use const STDIN;
|
||||
use const STDOUT;
|
||||
use const STR_PAD_LEFT;
|
||||
|
||||
require_once dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
function replaceVersion(string $versionInfoPath, string $newVersion, bool $isDev, string $channel) : void{
|
||||
$versionInfo = Filesystem::fileGetContents($versionInfoPath);
|
||||
$versionInfo = preg_replace(
|
||||
$pattern = '/^([\t ]*public )?const BASE_VERSION = "(\d+)\.(\d+)\.(\d+)(?:-(.*))?";$/m',
|
||||
'$1const BASE_VERSION = "' . $newVersion . '";',
|
||||
$versionInfo
|
||||
);
|
||||
$versionInfo = preg_replace(
|
||||
'/^([\t ]*public )?const IS_DEVELOPMENT_BUILD = (?:true|false);$/m',
|
||||
'$1const IS_DEVELOPMENT_BUILD = ' . ($isDev ? 'true' : 'false') . ';',
|
||||
$versionInfo
|
||||
);
|
||||
$versionInfo = preg_replace(
|
||||
'/^([\t ]*public )?const BUILD_CHANNEL = ".*";$/m',
|
||||
'$1const BUILD_CHANNEL = "' . $channel . '";',
|
||||
$versionInfo
|
||||
);
|
||||
file_put_contents($versionInfoPath, $versionInfo);
|
||||
}
|
||||
|
||||
const ACCEPTED_OPTS = [
|
||||
"current" => "Version to insert and tag",
|
||||
"next" => "Version to put in the file after tagging",
|
||||
"channel" => "Release channel to post this build into"
|
||||
];
|
||||
|
||||
function systemWrapper(string $command, string $errorMessage) : void{
|
||||
system($command, $result);
|
||||
if($result !== 0){
|
||||
echo "error: $errorMessage; aborting\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function main() : void{
|
||||
$filteredOpts = [];
|
||||
$postCommitOnly = false;
|
||||
foreach(Utils::stringifyKeys(getopt("", ["current:", "next:", "channel:", "help", "post"])) as $optName => $optValue){
|
||||
if($optName === "help"){
|
||||
fwrite(STDOUT, "Options:\n");
|
||||
|
||||
$maxLength = max(array_map(fn(string $str) => strlen($str), array_keys(ACCEPTED_OPTS)));
|
||||
foreach(ACCEPTED_OPTS as $acceptedName => $description){
|
||||
fwrite(STDOUT, str_pad("--$acceptedName", $maxLength + 4, " ", STR_PAD_LEFT) . ": $description\n");
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
if($optName === "post"){
|
||||
$postCommitOnly = true;
|
||||
continue;
|
||||
}
|
||||
if(!is_string($optValue)){
|
||||
fwrite(STDERR, "--$optName expects exactly 1 value\n");
|
||||
exit(1);
|
||||
}
|
||||
$filteredOpts[$optName] = $optValue;
|
||||
}
|
||||
|
||||
$channel = $filteredOpts["channel"] ?? null;
|
||||
if(isset($filteredOpts["current"])){
|
||||
$currentVer = new VersionString($filteredOpts["current"]);
|
||||
}else{
|
||||
$currentVer = new VersionString(VersionInfo::BASE_VERSION);
|
||||
}
|
||||
|
||||
$nextVer = isset($filteredOpts["next"]) ? new VersionString($filteredOpts["next"]) : null;
|
||||
|
||||
$suffix = $currentVer->getSuffix();
|
||||
if($suffix !== ""){
|
||||
if($channel === "stable"){
|
||||
fwrite(STDERR, "error: cannot release a suffixed build into the stable channel\n");
|
||||
exit(1);
|
||||
}
|
||||
if(preg_match('/^([A-Za-z]+)(\d+)$/', $suffix, $matches) !== 1){
|
||||
echo "error: invalid current version suffix \"$suffix\"; aborting\n";
|
||||
exit(1);
|
||||
}
|
||||
$nextVer ??= new VersionString(sprintf(
|
||||
"%u.%u.%u-%s%u",
|
||||
$currentVer->getMajor(),
|
||||
$currentVer->getMinor(),
|
||||
$currentVer->getPatch(),
|
||||
$matches[1],
|
||||
((int) $matches[2]) + 1
|
||||
));
|
||||
$channel ??= strtolower($matches[1]);
|
||||
}else{
|
||||
$nextVer ??= new VersionString(sprintf(
|
||||
"%u.%u.%u",
|
||||
$currentVer->getMajor(),
|
||||
$currentVer->getMinor(),
|
||||
$currentVer->getPatch() + 1
|
||||
));
|
||||
$channel ??= "stable";
|
||||
}
|
||||
|
||||
$versionInfoPath = dirname(__DIR__) . '/src/VersionInfo.php';
|
||||
|
||||
if($postCommitOnly){
|
||||
echo "Skipping release commit & tag. Bumping to next version $nextVer directly.\n";
|
||||
}else{
|
||||
echo "About to tag version $currentVer. Next version will be $nextVer.\n";
|
||||
echo "$currentVer will be published on release channel \"$channel\".\n";
|
||||
echo "please add appropriate notes to the changelog and press enter...";
|
||||
fgets(STDIN);
|
||||
systemWrapper('git add "' . dirname(__DIR__) . '/changelogs"', "failed to stage changelog changes");
|
||||
system('git diff --cached --quiet "' . dirname(__DIR__) . '/changelogs"', $result);
|
||||
if($result === 0){
|
||||
echo "error: no changelog changes detected; aborting\n";
|
||||
exit(1);
|
||||
}
|
||||
replaceVersion($versionInfoPath, $currentVer->getBaseVersion(), false, $channel);
|
||||
systemWrapper('git commit -m "Release ' . $currentVer->getBaseVersion() . '" --include "' . $versionInfoPath . '"', "failed to create release commit");
|
||||
systemWrapper('git tag ' . $currentVer->getBaseVersion(), "failed to create release tag");
|
||||
}
|
||||
|
||||
replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, $channel);
|
||||
systemWrapper('git add "' . $versionInfoPath . '"', "failed to stage changes for post-release commit");
|
||||
systemWrapper('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"', "failed to create post-release commit");
|
||||
}
|
||||
|
||||
main();
|
Submodule build/php updated: 5016e0a3d5...1549433797
@ -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';
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -114,3 +114,26 @@ Released 5th December 2024.
|
||||
|
||||
## Internals
|
||||
- Improved blockstate consistency check to detect tiles disappearing during refactors.
|
||||
|
||||
# 5.23.2
|
||||
Released 9th December 2024.
|
||||
|
||||
## General
|
||||
- Updated translations for Russian and Korean.
|
||||
|
||||
## Fixes
|
||||
- Fixed server build number.
|
||||
- Fixed some crashes being misreported as plugin-involved.
|
||||
|
||||
## Internals
|
||||
- Removed legacy `build/make-release.php` script. This script is no longer used, as all releases should now follow the PR workflow.
|
||||
|
||||
# 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.
|
||||
|
108
changelogs/5.24.md
Normal file
108
changelogs/5.24.md
Normal file
@ -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.
|
38
changelogs/5.25.md
Normal file
38
changelogs/5.25.md
Normal file
@ -0,0 +1,38 @@
|
||||
# 5.25.0
|
||||
Released 16th February 2025.
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.60. It also includes some minor API additions supporting new features in this version.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.21.60.
|
||||
- Removed support for earlier versions.
|
||||
|
||||
## Documentation
|
||||
- Fixed the documentation of `Utils::getOS()`. It now refers to the `Utils::OS_*` constants instead of a list of hardcoded strings.
|
||||
|
||||
## API
|
||||
### `pocketmine\inventory`
|
||||
This release allows plugins to decide which creative tab they want to add their new items to.
|
||||
It also allows creating new collapsible groups of items, and modifying or removing existing ones.
|
||||
|
||||
- The following new methods have been added:
|
||||
- `public CreativeInventory->getAllEntries() : list<CreativeInventoryEntry>` - 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.
|
@ -33,18 +33,18 @@
|
||||
"composer-runtime-api": "^2.0",
|
||||
"adhocore/json-comment": "~1.2.0",
|
||||
"netresearch/jsonmapper": "~v5.0.0",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-data": "~4.0.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
|
||||
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.50",
|
||||
"pocketmine/bedrock-protocol": "~36.0.0+bedrock-1.21.60",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
"pocketmine/errorhandler": "^0.7.0",
|
||||
"pocketmine/locale-data": "~2.22.0",
|
||||
"pocketmine/locale-data": "~2.24.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/math": "~1.0.0",
|
||||
"pocketmine/nbt": "~1.0.0",
|
||||
"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.11",
|
||||
"phpstan/phpstan-phpunit": "^1.1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.2.0",
|
||||
"phpstan/phpstan": "2.1.4",
|
||||
"phpstan/phpstan-phpunit": "^2.0.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.0",
|
||||
"phpunit/phpunit": "^10.5.24"
|
||||
},
|
||||
"autoload": {
|
||||
|
164
composer.lock
generated
164
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "732102eca72dc1d29e7b67dfbce07653",
|
||||
"content-hash": "663122b8f03ef5ec6718a419923a0851",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -178,16 +178,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-block-upgrade-schema",
|
||||
"version": "5.0.0",
|
||||
"version": "5.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
|
||||
"reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52"
|
||||
"reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
|
||||
"reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/2218512e4b91f5bfd09ef55f7a4c4b04e169e41a",
|
||||
"reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -198,22 +198,22 @@
|
||||
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
|
||||
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.0.0"
|
||||
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.1.0"
|
||||
},
|
||||
"time": "2024-11-03T14:13:50+00:00"
|
||||
"time": "2025-02-11T17:41:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-data",
|
||||
"version": "2.15.0+bedrock-1.21.50",
|
||||
"version": "4.0.0+bedrock-1.21.60",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockData.git",
|
||||
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad"
|
||||
"reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
|
||||
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/2e5f16ec2facac653f3f894f22eb831d880ba98e",
|
||||
"reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -224,9 +224,9 @@
|
||||
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockData/issues",
|
||||
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.50"
|
||||
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.60"
|
||||
},
|
||||
"time": "2024-12-04T12:59:12+00:00"
|
||||
"time": "2025-02-16T15:56:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-item-upgrade-schema",
|
||||
@ -256,16 +256,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-protocol",
|
||||
"version": "35.0.0+bedrock-1.21.50",
|
||||
"version": "36.0.0+bedrock-1.21.60",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||
"reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435"
|
||||
"reference": "2057de319c5c551001c2a544e08d1bc7727d9963"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
|
||||
"reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2057de319c5c551001c2a544e08d1bc7727d9963",
|
||||
"reference": "2057de319c5c551001c2a544e08d1bc7727d9963",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -278,10 +278,10 @@
|
||||
"ramsey/uuid": "^4.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.11.9",
|
||||
"phpstan/phpstan-phpunit": "^1.0.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.0.0",
|
||||
"phpunit/phpunit": "^9.5 || ^10.0"
|
||||
"phpstan/phpstan": "2.1.0",
|
||||
"phpstan/phpstan-phpunit": "^2.0.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.0",
|
||||
"phpunit/phpunit": "^9.5 || ^10.0 || ^11.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@ -296,9 +296,9 @@
|
||||
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/35.0.0+bedrock-1.21.50"
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/36.0.0+bedrock-1.21.60"
|
||||
},
|
||||
"time": "2024-12-04T13:02:00+00:00"
|
||||
"time": "2025-02-16T15:59:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
@ -471,16 +471,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
"version": "2.22.0",
|
||||
"version": "2.24.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d"
|
||||
"reference": "6ec5e92c77a2102b2692763733e4763012facae9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
|
||||
"reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/6ec5e92c77a2102b2692763733e4763012facae9",
|
||||
"reference": "6ec5e92c77a2102b2692763733e4763012facae9",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -488,9 +488,9 @@
|
||||
"description": "Language resources used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Language/issues",
|
||||
"source": "https://github.com/pmmp/Language/tree/2.22.0"
|
||||
"source": "https://github.com/pmmp/Language/tree/2.24.0"
|
||||
},
|
||||
"time": "2024-11-16T13:28:01+00:00"
|
||||
"time": "2025-02-16T20:46:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/log",
|
||||
@ -576,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": {
|
||||
@ -595,8 +595,8 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "1.10.25",
|
||||
"phpstan/phpstan-strict-rules": "^1.0",
|
||||
"phpstan/phpstan": "2.1.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"type": "library",
|
||||
@ -612,9 +612,9 @@
|
||||
"description": "PHP library for working with Named Binary Tags",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/NBT/issues",
|
||||
"source": "https://github.com/pmmp/NBT/tree/1.0.0"
|
||||
"source": "https://github.com/pmmp/NBT/tree/1.1.0"
|
||||
},
|
||||
"time": "2023-07-14T13:01:49+00:00"
|
||||
"time": "2025-02-01T21:20:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/raklib",
|
||||
@ -1013,8 +1013,8 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -1092,8 +1092,8 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@ -1210,16 +1210,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v5.3.1",
|
||||
"version": "v5.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
|
||||
"reference": "447a020a1f875a434d62f2a401f53b82a396e494"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
|
||||
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494",
|
||||
"reference": "447a020a1f875a434d62f2a401f53b82a396e494",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1262,9 +1262,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0"
|
||||
},
|
||||
"time": "2024-10-08T18:51:32+00:00"
|
||||
"time": "2024-12-30T11:07:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
@ -1386,20 +1386,20 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.11.11",
|
||||
"version": "2.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3"
|
||||
"reference": "8f99e18eb775dbaf6460c95fa0b65312da9c746a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3",
|
||||
"reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8f99e18eb775dbaf6460c95fa0b65312da9c746a",
|
||||
"reference": "8f99e18eb775dbaf6460c95fa0b65312da9c746a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2|^8.0"
|
||||
"php": "^7.4|^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan-shim": "*"
|
||||
@ -1440,34 +1440,33 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-08-19T14:37:29+00:00"
|
||||
"time": "2025-02-10T08:25:21+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": {
|
||||
@ -1490,34 +1489,33 @@
|
||||
"description": "PHPUnit extensions and rules for PHPStan",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0"
|
||||
"source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.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": {
|
||||
@ -1539,9 +1537,9 @@
|
||||
"description": "Extra strict and opinionated rules for PHPStan",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0"
|
||||
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3"
|
||||
},
|
||||
"time": "2024-04-20T06:37:51+00:00"
|
||||
"time": "2025-01-21T10:52:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@ -1866,16 +1864,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "10.5.38",
|
||||
"version": "10.5.44",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132"
|
||||
"reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132",
|
||||
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1381c62769be4bb88fa4c5aec1366c7c66ca4f36",
|
||||
"reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1885,7 +1883,7 @@
|
||||
"ext-mbstring": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"myclabs/deep-copy": "^1.12.0",
|
||||
"myclabs/deep-copy": "^1.12.1",
|
||||
"phar-io/manifest": "^2.0.4",
|
||||
"phar-io/version": "^3.2.1",
|
||||
"php": ">=8.1",
|
||||
@ -1947,7 +1945,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.44"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1963,7 +1961,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-28T13:06:21+00:00"
|
||||
"time": "2025-01-31T07:00:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
@ -19,6 +19,7 @@ rules:
|
||||
parameters:
|
||||
level: 9
|
||||
checkMissingCallableSignature: true
|
||||
rememberPossiblyImpureFunctionValues: false #risky to remember these, better for performance to avoid repeated calls anyway
|
||||
treatPhpDocTypesAsCertain: false
|
||||
bootstrapFiles:
|
||||
- tests/phpstan/bootstrap.php
|
||||
@ -31,6 +32,7 @@ parameters:
|
||||
paths:
|
||||
- build
|
||||
- src
|
||||
- tests/phpstan/DummyPluginOwned.php
|
||||
- tests/phpstan/rules
|
||||
- tests/phpunit
|
||||
- tests/plugins/TesterPlugin
|
||||
@ -44,6 +46,7 @@ parameters:
|
||||
- pocketmine\DEBUG
|
||||
- pocketmine\IS_DEVELOPMENT_BUILD
|
||||
stubFiles:
|
||||
- tests/phpstan/stubs/chunkutils2.stub
|
||||
- tests/phpstan/stubs/JsonMapper.stub
|
||||
- tests/phpstan/stubs/leveldb.stub
|
||||
- tests/phpstan/stubs/pmmpthread.stub
|
||||
|
@ -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.
|
||||
|
103
src/GarbageCollectorManager.php
Normal file
103
src/GarbageCollectorManager.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use function gc_collect_cycles;
|
||||
use function gc_disable;
|
||||
use function gc_status;
|
||||
use function hrtime;
|
||||
use function max;
|
||||
use function min;
|
||||
use function number_format;
|
||||
|
||||
/**
|
||||
* Allows threads to manually trigger the cyclic garbage collector using a threshold like PHP's own garbage collector,
|
||||
* but triggered at a time that suits the thread instead of in random code pathways.
|
||||
*
|
||||
* The GC trigger behaviour in this class was adapted from Zend/zend_gc.c as of PHP 8.3.14.
|
||||
*/
|
||||
final class GarbageCollectorManager{
|
||||
//TODO: These values could be adjusted to better suit PM, but for now we just want to mirror PHP GC to minimize
|
||||
//behavioural changes.
|
||||
private const GC_THRESHOLD_TRIGGER = 100;
|
||||
private const GC_THRESHOLD_MAX = 1_000_000_000;
|
||||
private const GC_THRESHOLD_DEFAULT = 10_001;
|
||||
private const GC_THRESHOLD_STEP = 10_000;
|
||||
|
||||
private int $threshold = self::GC_THRESHOLD_DEFAULT;
|
||||
private int $collectionTimeTotalNs = 0;
|
||||
|
||||
private \Logger $logger;
|
||||
private TimingsHandler $timings;
|
||||
|
||||
public function __construct(
|
||||
\Logger $logger,
|
||||
?TimingsHandler $parentTimings,
|
||||
){
|
||||
gc_disable();
|
||||
$this->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;
|
||||
}
|
||||
}
|
305
src/MemoryDump.php
Normal file
305
src/MemoryDump.php
Normal file
@ -0,0 +1,305 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine;
|
||||
|
||||
use pocketmine\utils\Utils;
|
||||
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_disable;
|
||||
use function gc_enable;
|
||||
use function gc_enabled;
|
||||
use function get_class;
|
||||
use function get_declared_classes;
|
||||
use function get_defined_functions;
|
||||
use function ini_get;
|
||||
use function ini_set;
|
||||
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 mkdir;
|
||||
use function print_r;
|
||||
use function spl_object_hash;
|
||||
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;
|
||||
|
||||
final class MemoryDump{
|
||||
|
||||
private function __construct(){
|
||||
//NOOP
|
||||
}
|
||||
|
||||
/**
|
||||
* Static memory dumper accessible from any thread.
|
||||
*/
|
||||
public static function dumpMemory(mixed $startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
|
||||
$hardLimit = Utils::assumeNotFalse(ini_get('memory_limit'), "memory_limit INI directive should always exist");
|
||||
ini_set('memory_limit', '-1');
|
||||
$gcEnabled = gc_enabled();
|
||||
gc_disable();
|
||||
|
||||
if(!file_exists($outputFolder)){
|
||||
mkdir($outputFolder, 0777, true);
|
||||
}
|
||||
|
||||
$obData = Utils::assumeNotFalse(fopen(Path::join($outputFolder, "objects.js"), "wb+"));
|
||||
|
||||
$objects = [];
|
||||
|
||||
$refCounts = [];
|
||||
|
||||
$instanceCounts = [];
|
||||
|
||||
$staticProperties = [];
|
||||
$staticCount = 0;
|
||||
|
||||
$functionStaticVars = [];
|
||||
$functionStaticVarsCount = 0;
|
||||
|
||||
foreach(get_declared_classes() as $className){
|
||||
$reflection = new \ReflectionClass($className);
|
||||
$staticProperties[$className] = [];
|
||||
foreach($reflection->getProperties() as $property){
|
||||
if(!$property->isStatic() || $property->getDeclaringClass()->getName() !== $className){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!$property->isInitialized()){
|
||||
continue;
|
||||
}
|
||||
|
||||
$staticCount++;
|
||||
$staticProperties[$className][$property->getName()] = self::continueDump($property->getValue(), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
|
||||
if(count($staticProperties[$className]) === 0){
|
||||
unset($staticProperties[$className]);
|
||||
}
|
||||
|
||||
foreach($reflection->getMethods() as $method){
|
||||
if($method->getDeclaringClass()->getName() !== $reflection->getName()){
|
||||
continue;
|
||||
}
|
||||
$methodStatics = [];
|
||||
foreach(Utils::promoteKeys($method->getStaticVariables()) as $name => $variable){
|
||||
$methodStatics[$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
if(count($methodStatics) > 0){
|
||||
$functionStaticVars[$className . "::" . $method->getName()] = $methodStatics;
|
||||
$functionStaticVarsCount += count($functionStaticVars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents(Path::join($outputFolder, "staticProperties.js"), json_encode($staticProperties, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
||||
$logger->info("Wrote $staticCount static properties");
|
||||
|
||||
$globalVariables = [];
|
||||
$globalCount = 0;
|
||||
|
||||
$ignoredGlobals = [
|
||||
'GLOBALS' => true,
|
||||
'_SERVER' => true,
|
||||
'_REQUEST' => true,
|
||||
'_POST' => true,
|
||||
'_GET' => true,
|
||||
'_FILES' => true,
|
||||
'_ENV' => true,
|
||||
'_COOKIE' => true,
|
||||
'_SESSION' => true
|
||||
];
|
||||
|
||||
foreach(Utils::promoteKeys($GLOBALS) as $varName => $value){
|
||||
if(isset($ignoredGlobals[$varName])){
|
||||
continue;
|
||||
}
|
||||
|
||||
$globalCount++;
|
||||
$globalVariables[$varName] = self::continueDump($value, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
|
||||
file_put_contents(Path::join($outputFolder, "globalVariables.js"), json_encode($globalVariables, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
||||
$logger->info("Wrote $globalCount global variables");
|
||||
|
||||
foreach(get_defined_functions()["user"] as $function){
|
||||
$reflect = new \ReflectionFunction($function);
|
||||
|
||||
$vars = [];
|
||||
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $varName => $variable){
|
||||
$vars[$varName] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
if(count($vars) > 0){
|
||||
$functionStaticVars[$function] = $vars;
|
||||
$functionStaticVarsCount += count($vars);
|
||||
}
|
||||
}
|
||||
file_put_contents(Path::join($outputFolder, 'functionStaticVars.js'), json_encode($functionStaticVars, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
||||
$logger->info("Wrote $functionStaticVarsCount function static variables");
|
||||
|
||||
$data = self::continueDump($startingObject, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
|
||||
do{
|
||||
$continue = false;
|
||||
foreach(Utils::stringifyKeys($objects) as $hash => $object){
|
||||
if(!is_object($object)){
|
||||
continue;
|
||||
}
|
||||
$continue = true;
|
||||
|
||||
$className = get_class($object);
|
||||
if(!isset($instanceCounts[$className])){
|
||||
$instanceCounts[$className] = 1;
|
||||
}else{
|
||||
$instanceCounts[$className]++;
|
||||
}
|
||||
|
||||
$objects[$hash] = true;
|
||||
$info = [
|
||||
"information" => "$hash@$className",
|
||||
];
|
||||
if($object instanceof \Closure){
|
||||
$info["definition"] = Utils::getNiceClosureName($object);
|
||||
$info["referencedVars"] = [];
|
||||
$reflect = new \ReflectionFunction($object);
|
||||
if(($closureThis = $reflect->getClosureThis()) !== null){
|
||||
$info["this"] = self::continueDump($closureThis, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
|
||||
foreach(Utils::promoteKeys($reflect->getStaticVariables()) as $name => $variable){
|
||||
$info["referencedVars"][$name] = self::continueDump($variable, $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
}else{
|
||||
$reflection = new \ReflectionObject($object);
|
||||
|
||||
$info["properties"] = [];
|
||||
|
||||
for($original = $reflection; $reflection !== false; $reflection = $reflection->getParentClass()){
|
||||
foreach($reflection->getProperties() as $property){
|
||||
if($property->isStatic()){
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $property->getName();
|
||||
if($reflection !== $original){
|
||||
if($property->isPrivate()){
|
||||
$name = $reflection->getName() . ":" . $name;
|
||||
}else{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(!$property->isInitialized($object)){
|
||||
continue;
|
||||
}
|
||||
|
||||
$info["properties"][$name] = self::continueDump($property->getValue($object), $objects, $refCounts, 0, $maxNesting, $maxStringSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fwrite($obData, json_encode($info, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n");
|
||||
}
|
||||
|
||||
}while($continue);
|
||||
|
||||
$logger->info("Wrote " . count($objects) . " objects");
|
||||
|
||||
fclose($obData);
|
||||
|
||||
file_put_contents(Path::join($outputFolder, "serverEntry.js"), json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
||||
file_put_contents(Path::join($outputFolder, "referenceCounts.js"), json_encode($refCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
||||
|
||||
arsort($instanceCounts, SORT_NUMERIC);
|
||||
file_put_contents(Path::join($outputFolder, "instanceCounts.js"), json_encode($instanceCounts, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
||||
|
||||
$logger->info("Finished!");
|
||||
|
||||
ini_set('memory_limit', $hardLimit);
|
||||
if($gcEnabled){
|
||||
gc_enable();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object[]|true[] $objects reference parameter
|
||||
* @param int[] $refCounts reference parameter
|
||||
*
|
||||
* @phpstan-param array<string, object|true> $objects
|
||||
* @phpstan-param array<string, int> $refCounts
|
||||
* @phpstan-param-out array<string, object|true> $objects
|
||||
* @phpstan-param-out array<string, int> $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;
|
||||
}
|
||||
}
|
@ -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(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);
|
||||
gc_enable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object[] $objects reference parameter
|
||||
* @param int[] $refCounts reference parameter
|
||||
*
|
||||
* @phpstan-param array<string, object> $objects
|
||||
* @phpstan-param array<string, int> $refCounts
|
||||
* @phpstan-param-out array<string, object> $objects
|
||||
* @phpstan-param-out array<string, int> $refCounts
|
||||
*/
|
||||
private static function continueDump(mixed $from, array &$objects, array &$refCounts, int $recursion, int $maxNesting, int $maxStringSize) : mixed{
|
||||
if($maxNesting <= 0){
|
||||
return "(error) NESTING LIMIT REACHED";
|
||||
}
|
||||
|
||||
--$maxNesting;
|
||||
|
||||
if(is_object($from)){
|
||||
if(!isset($objects[$hash = spl_object_hash($from)])){
|
||||
$objects[$hash] = $from;
|
||||
$refCounts[$hash] = 0;
|
||||
}
|
||||
|
||||
++$refCounts[$hash];
|
||||
|
||||
$data = "(object) $hash";
|
||||
}elseif(is_array($from)){
|
||||
if($recursion >= 5){
|
||||
return "(error) ARRAY RECURSION LIMIT REACHED";
|
||||
}
|
||||
$data = [];
|
||||
$numeric = 0;
|
||||
foreach(Utils::promoteKeys($from) as $key => $value){
|
||||
$data[$numeric] = [
|
||||
"k" => self::continueDump($key, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
|
||||
"v" => self::continueDump($value, $objects, $refCounts, $recursion + 1, $maxNesting, $maxStringSize),
|
||||
];
|
||||
$numeric++;
|
||||
}
|
||||
}elseif(is_string($from)){
|
||||
$data = "(string) len(" . strlen($from) . ") " . substr(Utils::printable($from), 0, $maxStringSize);
|
||||
}elseif(is_resource($from)){
|
||||
$data = "(resource) " . print_r($from, true);
|
||||
}elseif(is_float($from)){
|
||||
$data = "(float) $from";
|
||||
}else{
|
||||
$data = $from;
|
||||
}
|
||||
|
||||
return $data;
|
||||
MemoryDump::dumpMemory($startingObject, $outputFolder, $maxNesting, $maxStringSize, $logger);
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
@ -138,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;
|
||||
@ -918,6 +920,7 @@ class Server{
|
||||
TimingsHandler::getCollectCallbacks()->add(function() : array{
|
||||
$promises = [];
|
||||
foreach($this->asyncPool->getRunningWorkers() as $workerId){
|
||||
/** @phpstan-var PromiseResolver<list<string>> $resolver */
|
||||
$resolver = new PromiseResolver();
|
||||
$this->asyncPool->submitTaskToWorker(new TimingsCollectionTask($resolver), $workerId);
|
||||
|
||||
@ -1003,7 +1006,7 @@ class Server{
|
||||
|
||||
$this->commandMap = new SimpleCommandMap($this);
|
||||
|
||||
$this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes"));
|
||||
$this->craftingManager = CraftingManagerFromDataHelper::make(BedrockDataFiles::RECIPES);
|
||||
|
||||
$this->resourceManager = new ResourcePackManager(Path::join($this->dataPath, "resource_packs"), $this->logger);
|
||||
|
||||
@ -1013,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();
|
||||
@ -1174,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");
|
||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "5.23.2";
|
||||
public const BASE_VERSION = "5.25.1";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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)];
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 [];
|
||||
}
|
||||
|
@ -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 [];
|
||||
}
|
||||
|
@ -76,9 +76,6 @@ class Bed extends Transparent{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 16)];
|
||||
}
|
||||
|
@ -75,7 +75,10 @@ class Block{
|
||||
protected BlockTypeInfo $typeInfo;
|
||||
protected Position $position;
|
||||
|
||||
/** @var AxisAlignedBB[]|null */
|
||||
/**
|
||||
* @var AxisAlignedBB[]|null
|
||||
* @phpstan-var list<AxisAlignedBB>|null
|
||||
*/
|
||||
protected ?array $collisionBoxes = null;
|
||||
|
||||
private int $requiredBlockItemStateDataBits;
|
||||
@ -907,6 +910,7 @@ class Block{
|
||||
* - anti-cheat checks in plugins
|
||||
*
|
||||
* @return AxisAlignedBB[]
|
||||
* @phpstan-return list<AxisAlignedBB>
|
||||
*/
|
||||
final public function getCollisionBoxes() : array{
|
||||
if($this->collisionBoxes === null){
|
||||
@ -931,6 +935,7 @@ class Block{
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
* @phpstan-return list<AxisAlignedBB>
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()];
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -765,8 +765,29 @@ final class BlockTypeIds{
|
||||
public const COPPER_TRAPDOOR = 10735;
|
||||
public const CHISELED_COPPER = 10736;
|
||||
public const COPPER_GRATE = 10737;
|
||||
public const PALE_OAK_BUTTON = 10738;
|
||||
public const PALE_OAK_DOOR = 10739;
|
||||
public const PALE_OAK_FENCE = 10740;
|
||||
public const PALE_OAK_FENCE_GATE = 10741;
|
||||
public const PALE_OAK_LEAVES = 10742;
|
||||
public const PALE_OAK_LOG = 10743;
|
||||
public const PALE_OAK_PLANKS = 10744;
|
||||
public const PALE_OAK_PRESSURE_PLATE = 10745;
|
||||
public const PALE_OAK_SIGN = 10746;
|
||||
public const PALE_OAK_SLAB = 10747;
|
||||
public const PALE_OAK_STAIRS = 10748;
|
||||
public const PALE_OAK_TRAPDOOR = 10749;
|
||||
public const PALE_OAK_WALL_SIGN = 10750;
|
||||
public const PALE_OAK_WOOD = 10751;
|
||||
public const 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 = 10738;
|
||||
public const FIRST_UNUSED_BLOCK_ID = 10759;
|
||||
|
||||
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;
|
||||
|
||||
|
@ -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)];
|
||||
|
@ -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()
|
||||
|
@ -36,9 +36,6 @@ class CakeWithCandle extends BaseCake{
|
||||
onInteract as onInteractCandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [
|
||||
AxisAlignedBB::one()
|
||||
|
@ -36,9 +36,6 @@ class Carpet extends Flowable{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 15 / 16)];
|
||||
}
|
||||
|
@ -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)];
|
||||
|
@ -34,11 +34,16 @@ use function mt_rand;
|
||||
final class ChorusPlant extends Flowable{
|
||||
use StaticSupportTrait;
|
||||
|
||||
/**
|
||||
* @var true[]
|
||||
* @phpstan-var array<int, true>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -62,9 +62,6 @@ class DaylightSensor extends Transparent{
|
||||
return 300;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 10 / 16)];
|
||||
}
|
||||
|
@ -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)];
|
||||
|
@ -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)];
|
||||
}
|
||||
|
@ -50,9 +50,6 @@ class EndPortalFrame extends Opaque{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 3 / 16)];
|
||||
}
|
||||
|
@ -52,9 +52,6 @@ class EndRod extends Flowable{
|
||||
return 14;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
$myAxis = Facing::axis($this->facing);
|
||||
|
||||
|
@ -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)];
|
||||
|
@ -94,9 +94,6 @@ class Farmland extends Transparent{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)];
|
||||
}
|
||||
|
@ -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]);
|
||||
|
@ -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)];
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Vector3;
|
||||
|
||||
/**
|
||||
@ -46,9 +45,6 @@ abstract class Flowable extends Transparent{
|
||||
parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [];
|
||||
}
|
||||
|
@ -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)];
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ use pocketmine\block\utils\MultiAnySupportTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\item\Fertilizer;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
@ -47,9 +46,6 @@ class GlowLichen extends Transparent{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [];
|
||||
}
|
||||
|
@ -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)];
|
||||
}
|
||||
|
@ -58,9 +58,6 @@ class Ladder extends Transparent{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trim($this->facing, 13 / 16)];
|
||||
}
|
||||
|
@ -59,9 +59,6 @@ class Lantern extends Transparent{
|
||||
return $this->lightLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [
|
||||
AxisAlignedBB::one()
|
||||
|
@ -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;
|
||||
|
@ -30,7 +30,6 @@ use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\block\BlockSpreadEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\utils\Utils;
|
||||
@ -89,9 +88,6 @@ abstract class Liquid extends Transparent{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [];
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 [];
|
||||
}
|
||||
|
@ -79,9 +79,6 @@ class RedstoneComparator extends Flowable{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 8)];
|
||||
}
|
||||
|
@ -62,9 +62,6 @@ class RedstoneRepeater extends Flowable{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 8)];
|
||||
}
|
||||
|
54
src/block/ResinClump.php
Normal file
54
src/block/ResinClump.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\MultiAnySupportTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
|
||||
final class ResinClump extends Transparent{
|
||||
use MultiAnySupportTrait;
|
||||
|
||||
public function isSolid() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
public function canBeReplaced() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
protected function getInitialPlaceFaces(Block $blockReplace) : array{
|
||||
return $blockReplace instanceof ResinClump ? $blockReplace->faces : [];
|
||||
}
|
||||
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [];
|
||||
}
|
||||
}
|
@ -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<int, Block>
|
||||
@ -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<int, int>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 [];
|
||||
}
|
||||
|
@ -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()];
|
||||
|
@ -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)];
|
||||
|
@ -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)];
|
||||
}
|
||||
|
@ -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])){
|
||||
|
@ -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)];
|
||||
}
|
||||
|
@ -79,6 +79,10 @@ use function strtolower;
|
||||
* @see build/generate-registry-annotations.php
|
||||
* @generate-registry-docblock
|
||||
*
|
||||
* @method static Sapling SAPLING(\pocketmine\block\utils\SaplingType $saplingType)
|
||||
* @method static Leaves LEAVES(\pocketmine\block\utils\LeavesType $leavesType)
|
||||
* @method static FloorSign SIGN(\pocketmine\block\utils\WoodType $woodType)
|
||||
* @method static WallSign WALL_SIGN(\pocketmine\block\utils\WoodType $woodType)
|
||||
* @method static WoodenButton ACACIA_BUTTON()
|
||||
* @method static WoodenDoor ACACIA_DOOR()
|
||||
* @method static WoodenFence ACACIA_FENCE()
|
||||
@ -191,6 +195,7 @@ use function strtolower;
|
||||
* @method static Opaque CHISELED_POLISHED_BLACKSTONE()
|
||||
* @method static SimplePillar CHISELED_QUARTZ()
|
||||
* @method static Opaque CHISELED_RED_SANDSTONE()
|
||||
* @method static Opaque CHISELED_RESIN_BRICKS()
|
||||
* @method static Opaque CHISELED_SANDSTONE()
|
||||
* @method static Opaque CHISELED_STONE_BRICKS()
|
||||
* @method static Opaque CHISELED_TUFF()
|
||||
@ -590,6 +595,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()
|
||||
@ -673,6 +692,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()
|
||||
@ -858,12 +883,12 @@ final class VanillaBlocks{
|
||||
self::register("bedrock", fn(BID $id) => new Bedrock($id, "Bedrock", new Info(BreakInfo::indestructible())));
|
||||
|
||||
self::register("beetroots", fn(BID $id) => new Beetroot($id, "Beetroot Block", new Info(BreakInfo::instant())));
|
||||
self::register("bell", fn(BID $id) => new Bell($id, "Bell", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))), TileBell::class);
|
||||
self::register("bell", fn(BID $id) => new Bell($id, "Bell", new Info(BreakInfo::pickaxe(5.0))), TileBell::class);
|
||||
self::register("blue_ice", fn(BID $id) => new BlueIce($id, "Blue Ice", new Info(BreakInfo::pickaxe(2.8))));
|
||||
self::register("bone_block", fn(BID $id) => new BoneBlock($id, "Bone Block", new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD))));
|
||||
self::register("bookshelf", fn(BID $id) => new Bookshelf($id, "Bookshelf", new Info(BreakInfo::axe(1.5))));
|
||||
self::register("chiseled_bookshelf", fn(BID $id) => new ChiseledBookshelf($id, "Chiseled Bookshelf", new Info(BreakInfo::axe(1.5))), TileChiseledBookshelf::class);
|
||||
self::register("brewing_stand", fn(BID $id) => new BrewingStand($id, "Brewing Stand", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))), TileBrewingStand::class);
|
||||
self::register("brewing_stand", fn(BID $id) => new BrewingStand($id, "Brewing Stand", new Info(BreakInfo::pickaxe(0.5))), TileBrewingStand::class);
|
||||
|
||||
$bricksBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
|
||||
self::register("brick_stairs", fn(BID $id) => new Stair($id, "Brick Stairs", $bricksBreakInfo));
|
||||
@ -921,7 +946,7 @@ final class VanillaBlocks{
|
||||
self::register("end_stone_bricks", fn(BID $id) => new Opaque($id, "End Stone Bricks", $endBrickBreakInfo));
|
||||
self::register("end_stone_brick_stairs", fn(BID $id) => new Stair($id, "End Stone Brick Stairs", $endBrickBreakInfo));
|
||||
|
||||
self::register("ender_chest", fn(BID $id) => new EnderChest($id, "Ender Chest", new Info(BreakInfo::pickaxe(22.5, ToolTier::WOOD, 3000.0))), TileEnderChest::class);
|
||||
self::register("ender_chest", fn(BID $id) => new EnderChest($id, "Ender Chest", new Info(BreakInfo::pickaxe(22.5, blastResistance: 3000.0))), TileEnderChest::class);
|
||||
self::register("farmland", fn(BID $id) => new Farmland($id, "Farmland", new Info(BreakInfo::shovel(0.6), [Tags::DIRT])));
|
||||
self::register("fire", fn(BID $id) => new Fire($id, "Fire Block", new Info(BreakInfo::instant(), [Tags::FIRE])));
|
||||
|
||||
@ -977,9 +1002,9 @@ final class VanillaBlocks{
|
||||
$ironBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::STONE, 30.0));
|
||||
self::register("iron", fn(BID $id) => new Opaque($id, "Iron Block", $ironBreakInfo));
|
||||
self::register("iron_bars", fn(BID $id) => new Thin($id, "Iron Bars", $ironBreakInfo));
|
||||
$ironDoorBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD, 25.0));
|
||||
self::register("iron_door", fn(BID $id) => new Door($id, "Iron Door", $ironDoorBreakInfo));
|
||||
self::register("iron_trapdoor", fn(BID $id) => new Trapdoor($id, "Iron Trapdoor", $ironDoorBreakInfo));
|
||||
|
||||
self::register("iron_door", fn(BID $id) => new Door($id, "Iron Door", new Info(BreakInfo::pickaxe(5.0))));
|
||||
self::register("iron_trapdoor", fn(BID $id) => new Trapdoor($id, "Iron Trapdoor", new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD))));
|
||||
|
||||
$itemFrameInfo = new Info(new BreakInfo(0.25));
|
||||
self::register("item_frame", fn(BID $id) => new ItemFrame($id, "Item Frame", $itemFrameInfo), TileItemFrame::class);
|
||||
@ -988,7 +1013,7 @@ final class VanillaBlocks{
|
||||
self::register("jukebox", fn(BID $id) => new Jukebox($id, "Jukebox", new Info(BreakInfo::axe(0.8))), TileJukebox::class); //TODO: in PC the hardness is 2.0, not 0.8, unsure if this is a MCPE bug or not
|
||||
self::register("ladder", fn(BID $id) => new Ladder($id, "Ladder", new Info(BreakInfo::axe(0.4))));
|
||||
|
||||
$lanternBreakInfo = new Info(BreakInfo::pickaxe(5.0, ToolTier::WOOD));
|
||||
$lanternBreakInfo = new Info(BreakInfo::pickaxe(5.0));
|
||||
self::register("lantern", fn(BID $id) => new Lantern($id, "Lantern", $lanternBreakInfo, 15));
|
||||
self::register("soul_lantern", fn(BID $id) => new Lantern($id, "Soul Lantern", $lanternBreakInfo, 10));
|
||||
|
||||
@ -1123,7 +1148,7 @@ final class VanillaBlocks{
|
||||
self::register("mossy_stone_brick_stairs", fn(BID $id) => new Stair($id, "Mossy Stone Brick Stairs", $stoneBreakInfo));
|
||||
self::register("stone_button", fn(BID $id) => new StoneButton($id, "Stone Button", new Info(BreakInfo::pickaxe(0.5))));
|
||||
self::register("stonecutter", fn(BID $id) => new Stonecutter($id, "Stonecutter", new Info(BreakInfo::pickaxe(3.5))));
|
||||
self::register("stone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, "Stone Pressure Plate", new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD))));
|
||||
self::register("stone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, "Stone Pressure Plate", new Info(BreakInfo::pickaxe(0.5))));
|
||||
|
||||
//TODO: in the future this won't be the same for all the types
|
||||
$stoneSlabBreakInfo = new Info(BreakInfo::pickaxe(2.0, ToolTier::WOOD, 30.0));
|
||||
@ -1179,7 +1204,7 @@ final class VanillaBlocks{
|
||||
self::register("water", fn(BID $id) => new Water($id, "Water", new Info(BreakInfo::indestructible(500.0))));
|
||||
self::register("lily_pad", fn(BID $id) => new WaterLily($id, "Lily Pad", new Info(BreakInfo::instant())));
|
||||
|
||||
$weightedPressurePlateBreakInfo = new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD));
|
||||
$weightedPressurePlateBreakInfo = new Info(BreakInfo::pickaxe(0.5));
|
||||
self::register("weighted_pressure_plate_heavy", fn(BID $id) => new WeightedPressurePlateHeavy(
|
||||
$id,
|
||||
"Weighted Pressure Plate Heavy",
|
||||
@ -1210,10 +1235,12 @@ final class VanillaBlocks{
|
||||
$name = $saplingType->getDisplayName();
|
||||
self::register(strtolower($saplingType->name) . "_sapling", fn(BID $id) => new Sapling($id, $name . " Sapling", $saplingTypeInfo, $saplingType));
|
||||
}
|
||||
self::registerOverloaded("sapling", SaplingType::class, Sapling::class);
|
||||
foreach(LeavesType::cases() as $leavesType){
|
||||
$name = $leavesType->getDisplayName();
|
||||
self::register(strtolower($leavesType->name) . "_leaves", fn(BID $id) => new Leaves($id, $name . " Leaves", $leavesBreakInfo, $leavesType));
|
||||
}
|
||||
self::registerOverloaded("leaves", LeavesType::class, Leaves::class);
|
||||
|
||||
$sandstoneBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD));
|
||||
self::register("red_sandstone_stairs", fn(BID $id) => new Stair($id, "Red Sandstone Stairs", $sandstoneBreakInfo));
|
||||
@ -1312,6 +1339,7 @@ final class VanillaBlocks{
|
||||
self::registerBlocksR17();
|
||||
self::registerBlocksR18();
|
||||
self::registerMudBlocks();
|
||||
self::registerResinBlocks();
|
||||
self::registerTuffBlocks();
|
||||
|
||||
self::registerCraftingTables();
|
||||
@ -1348,21 +1376,12 @@ final class VanillaBlocks{
|
||||
self::register($idName("pressure_plate"), fn(BID $id) => new WoodenPressurePlate($id, $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType, 20));
|
||||
self::register($idName("trapdoor"), fn(BID $id) => new WoodenTrapdoor($id, $name . " Trapdoor", $woodenDoorBreakInfo, $woodType));
|
||||
|
||||
$signAsItem = match($woodType){
|
||||
WoodType::OAK => VanillaItems::OAK_SIGN(...),
|
||||
WoodType::SPRUCE => VanillaItems::SPRUCE_SIGN(...),
|
||||
WoodType::BIRCH => VanillaItems::BIRCH_SIGN(...),
|
||||
WoodType::JUNGLE => VanillaItems::JUNGLE_SIGN(...),
|
||||
WoodType::ACACIA => VanillaItems::ACACIA_SIGN(...),
|
||||
WoodType::DARK_OAK => VanillaItems::DARK_OAK_SIGN(...),
|
||||
WoodType::MANGROVE => VanillaItems::MANGROVE_SIGN(...),
|
||||
WoodType::CRIMSON => VanillaItems::CRIMSON_SIGN(...),
|
||||
WoodType::WARPED => VanillaItems::WARPED_SIGN(...),
|
||||
WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...),
|
||||
};
|
||||
$signAsItem = fn() => VanillaItems::SIGN($woodType);
|
||||
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);
|
||||
}
|
||||
self::registerOverloaded("sign", WoodType::class, FloorSign::class);
|
||||
self::registerOverloaded("wall_sign", WoodType::class, WallSign::class);
|
||||
}
|
||||
|
||||
private static function registerMushroomBlocks() : void{
|
||||
@ -1583,7 +1602,7 @@ final class VanillaBlocks{
|
||||
$prefix = fn(string $thing) => "Polished Blackstone" . ($thing !== "" ? " $thing" : "");
|
||||
self::register("polished_blackstone", fn(BID $id) => new Opaque($id, $prefix(""), $blackstoneBreakInfo));
|
||||
self::register("polished_blackstone_button", fn(BID $id) => new StoneButton($id, $prefix("Button"), new Info(BreakInfo::pickaxe(0.5))));
|
||||
self::register("polished_blackstone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5, ToolTier::WOOD)), 20));
|
||||
self::register("polished_blackstone_pressure_plate", fn(BID $id) => new StonePressurePlate($id, $prefix("Pressure Plate"), new Info(BreakInfo::pickaxe(0.5)), 20));
|
||||
self::register("polished_blackstone_slab", fn(BID $id) => new Slab($id, $prefix(""), $slabBreakInfo));
|
||||
self::register("polished_blackstone_stairs", fn(BID $id) => new Stair($id, $prefix("Stairs"), $blackstoneBreakInfo));
|
||||
self::register("polished_blackstone_wall", fn(BID $id) => new Wall($id, $prefix("Wall"), $blackstoneBreakInfo));
|
||||
@ -1690,9 +1709,8 @@ final class VanillaBlocks{
|
||||
self::register("cut_copper_stairs", fn(BID $id) => new CopperStairs($id, "Cut Copper Stairs", $copperBreakInfo));
|
||||
self::register("copper_bulb", fn(BID $id) => new CopperBulb($id, "Copper Bulb", $copperBreakInfo));
|
||||
|
||||
$copperDoorBreakInfo = new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 30.0));
|
||||
self::register("copper_door", fn(BID $id) => new CopperDoor($id, "Copper Door", $copperDoorBreakInfo));
|
||||
self::register("copper_trapdoor", fn(BID $id) => new CopperTrapdoor($id, "Copper Trapdoor", $copperDoorBreakInfo));
|
||||
self::register("copper_door", fn(BID $id) => new CopperDoor($id, "Copper Door", new Info(BreakInfo::pickaxe(3.0, blastResistance: 30.0))));
|
||||
self::register("copper_trapdoor", fn(BID $id) => new CopperTrapdoor($id, "Copper Trapdoor", new Info(BreakInfo::pickaxe(3.0, ToolTier::STONE, 30.0))));
|
||||
|
||||
$candleBreakInfo = new Info(new BreakInfo(0.1));
|
||||
self::register("candle", fn(BID $id) => new Candle($id, "Candle", $candleBreakInfo));
|
||||
@ -1728,6 +1746,18 @@ final class VanillaBlocks{
|
||||
self::register("mud_brick_wall", fn(BID $id) => new Wall($id, "Mud Brick Wall", $mudBricksBreakInfo));
|
||||
}
|
||||
|
||||
private static function registerResinBlocks() : void{
|
||||
self::register("resin", fn(BID $id) => new Opaque($id, "Block of Resin", new Info(BreakInfo::instant())));
|
||||
self::register("resin_clump", fn(BID $id) => new ResinClump($id, "Resin Clump", new Info(BreakInfo::instant())));
|
||||
|
||||
$resinBricksInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD));
|
||||
self::register("resin_brick_slab", fn(BID $id) => new Slab($id, "Resin Brick", $resinBricksInfo));
|
||||
self::register("resin_brick_stairs", fn(BID $id) => new Stair($id, "Resin Brick Stairs", $resinBricksInfo));
|
||||
self::register("resin_brick_wall", fn(BID $id) => new Wall($id, "Resin Brick Wall", $resinBricksInfo));
|
||||
self::register("resin_bricks", fn(BID $id) => new Opaque($id, "Resin Bricks", $resinBricksInfo));
|
||||
self::register("chiseled_resin_bricks", fn(BID $id) => new Opaque($id, "Chiseled Resin Bricks", $resinBricksInfo));
|
||||
}
|
||||
|
||||
private static function registerTuffBlocks() : void{
|
||||
$tuffBreakInfo = new Info(BreakInfo::pickaxe(1.5, ToolTier::WOOD, 30.0));
|
||||
|
||||
|
@ -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)];
|
||||
}
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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()],
|
||||
|
@ -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",
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ use pocketmine\permission\PermissionManager;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\BroadcastLoggerForwarder;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use function array_values;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function str_replace;
|
||||
@ -80,6 +81,7 @@ abstract class Command{
|
||||
|
||||
/**
|
||||
* @param string[] $args
|
||||
* @phpstan-param list<string> $args
|
||||
*
|
||||
* @return mixed
|
||||
* @throws CommandException
|
||||
@ -212,6 +214,7 @@ abstract class Command{
|
||||
* @phpstan-param list<string> $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;
|
||||
|
@ -121,6 +121,7 @@ class FormattedCommandAlias extends Command{
|
||||
|
||||
/**
|
||||
* @param string[] $args
|
||||
* @phpstan-param list<string> $args
|
||||
*/
|
||||
private function buildCommand(string $formatString, array $args) : ?string{
|
||||
$index = 0;
|
||||
|
@ -73,6 +73,7 @@ use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_shift;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function str_contains;
|
||||
@ -163,7 +164,7 @@ class SimpleCommandMap implements CommandMap{
|
||||
unset($aliases[$index]);
|
||||
}
|
||||
}
|
||||
$command->setAliases($aliases);
|
||||
$command->setAliases(array_values($aliases));
|
||||
|
||||
if(!$registered){
|
||||
$command->setLabel($fallbackPrefix . ":" . $label);
|
||||
|
@ -46,6 +46,8 @@ use function fwrite;
|
||||
use function http_build_query;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
use function json_decode;
|
||||
use function mkdir;
|
||||
use function strtolower;
|
||||
@ -178,7 +180,7 @@ class TimingsCommand extends VanillaCommand{
|
||||
return;
|
||||
}
|
||||
$response = json_decode($result->getBody(), true);
|
||||
if(is_array($response) && isset($response["id"])){
|
||||
if(is_array($response) && isset($response["id"]) && (is_int($response["id"]) || is_string($response["id"]))){
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
|
||||
"https://" . $host . "/?id=" . $response["id"]));
|
||||
}else{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,6 @@ class CraftingManager{
|
||||
|
||||
/**
|
||||
* @param Item[] $items
|
||||
* @phpstan-param list<Item> $items
|
||||
*
|
||||
* @return Item[]
|
||||
* @phpstan-return list<Item>
|
||||
@ -135,7 +134,6 @@ class CraftingManager{
|
||||
|
||||
/**
|
||||
* @param Item[] $outputs
|
||||
* @phpstan-param list<Item> $outputs
|
||||
*/
|
||||
private static function hashOutputs(array $outputs) : string{
|
||||
$outputs = self::pack($outputs);
|
||||
|
@ -39,14 +39,16 @@ final class BedrockDataFiles{
|
||||
public const BLOCK_STATE_META_MAP_JSON = BEDROCK_DATA_PATH . '/block_state_meta_map.json';
|
||||
public const CANONICAL_BLOCK_STATES_NBT = BEDROCK_DATA_PATH . '/canonical_block_states.nbt';
|
||||
public const COMMAND_ARG_TYPES_JSON = BEDROCK_DATA_PATH . '/command_arg_types.json';
|
||||
public const 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';
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -45,8 +45,8 @@ final class BlockStateData{
|
||||
public const CURRENT_VERSION =
|
||||
(1 << 24) | //major
|
||||
(21 << 16) | //minor
|
||||
(40 << 8) | //patch
|
||||
(1); //revision
|
||||
(60 << 8) | //patch
|
||||
(33); //revision
|
||||
|
||||
public const TAG_NAME = "name";
|
||||
public const TAG_STATES = "states";
|
||||
|
@ -59,6 +59,7 @@ final class BlockStateNames{
|
||||
public const COVERED_BIT = "covered_bit";
|
||||
public const CRACKED_STATE = "cracked_state";
|
||||
public const CRAFTING = "crafting";
|
||||
public const CREAKING_HEART_STATE = "creaking_heart_state";
|
||||
public const DEAD_BIT = "dead_bit";
|
||||
public const DEPRECATED = "deprecated";
|
||||
public const DIRECTION = "direction";
|
||||
|
@ -56,6 +56,10 @@ final class BlockStateStringValues{
|
||||
public const CRACKED_STATE_MAX_CRACKED = "max_cracked";
|
||||
public const CRACKED_STATE_NO_CRACKS = "no_cracks";
|
||||
|
||||
public const CREAKING_HEART_STATE_AWAKE = "awake";
|
||||
public const CREAKING_HEART_STATE_DORMANT = "dormant";
|
||||
public const CREAKING_HEART_STATE_UPROOTED = "uprooted";
|
||||
|
||||
public const DRIPSTONE_THICKNESS_BASE = "base";
|
||||
public const DRIPSTONE_THICKNESS_FRUSTUM = "frustum";
|
||||
public const DRIPSTONE_THICKNESS_MERGE = "merge";
|
||||
|
@ -121,6 +121,7 @@ use pocketmine\block\RedstoneOre;
|
||||
use pocketmine\block\RedstoneRepeater;
|
||||
use pocketmine\block\RedstoneTorch;
|
||||
use pocketmine\block\RedstoneWire;
|
||||
use pocketmine\block\ResinClump;
|
||||
use pocketmine\block\RuntimeBlockStateRegistry;
|
||||
use pocketmine\block\Sapling;
|
||||
use pocketmine\block\SeaPickle;
|
||||
@ -704,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)));
|
||||
@ -740,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)));
|
||||
@ -795,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);
|
||||
@ -1036,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);
|
||||
@ -1720,6 +1739,13 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
|
||||
$this->mapStairs(Blocks::RED_SANDSTONE_STAIRS(), Ids::RED_SANDSTONE_STAIRS);
|
||||
$this->map(Blocks::RED_SANDSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RED_SANDSTONE_WALL)));
|
||||
$this->map(Blocks::RED_TORCH(), fn(Torch $block) => Helper::encodeTorch($block, Writer::create(Ids::COLORED_TORCH_RED)));
|
||||
$this->mapSlab(Blocks::RESIN_BRICK_SLAB(), Ids::RESIN_BRICK_SLAB, Ids::RESIN_BRICK_DOUBLE_SLAB);
|
||||
$this->map(Blocks::RESIN_BRICK_STAIRS(), fn(Stair $block) => Helper::encodeStairs($block, new Writer(Ids::RESIN_BRICK_STAIRS)));
|
||||
$this->map(Blocks::RESIN_BRICK_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::RESIN_BRICK_WALL)));
|
||||
$this->map(Blocks::RESIN_CLUMP(), function(ResinClump $block) : Writer{
|
||||
return Writer::create(Ids::RESIN_CLUMP)
|
||||
->writeFacingFlags($block->getFaces());
|
||||
});
|
||||
$this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, Writer::create(Ids::ROSE_BUSH)));
|
||||
$this->mapSlab(Blocks::SANDSTONE_SLAB(), Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB);
|
||||
$this->mapStairs(Blocks::SANDSTONE_STAIRS(), Ids::SANDSTONE_STAIRS);
|
||||
|
@ -131,7 +131,7 @@ final class BlockStateDeserializerHelper{
|
||||
//TODO: check if these need any special treatment to get the appropriate data to both halves of the door
|
||||
return $block
|
||||
->setTop($in->readBool(BlockStateNames::UPPER_BLOCK_BIT))
|
||||
->setFacing(Facing::rotateY($in->readLegacyHorizontalFacing(), false))
|
||||
->setFacing($in->readCardinalHorizontalFacing())
|
||||
->setHingeRight($in->readBool(BlockStateNames::DOOR_HINGE_BIT))
|
||||
->setOpen($in->readBool(BlockStateNames::OPEN_BIT));
|
||||
}
|
||||
@ -145,7 +145,7 @@ final class BlockStateDeserializerHelper{
|
||||
/** @throws BlockStateDeserializeException */
|
||||
public static function decodeFenceGate(FenceGate $block, BlockStateReader $in) : FenceGate{
|
||||
return $block
|
||||
->setFacing($in->readLegacyHorizontalFacing())
|
||||
->setFacing($in->readCardinalHorizontalFacing())
|
||||
->setInWall($in->readBool(BlockStateNames::IN_WALL_BIT))
|
||||
->setOpen($in->readBool(BlockStateNames::OPEN_BIT));
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ final class BlockStateSerializerHelper{
|
||||
public static function encodeDoor(Door $block, Writer $out) : Writer{
|
||||
return $out
|
||||
->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop())
|
||||
->writeLegacyHorizontalFacing(Facing::rotateY($block->getFacing(), true))
|
||||
->writeCardinalHorizontalFacing($block->getFacing())
|
||||
->writeBool(BlockStateNames::DOOR_HINGE_BIT, $block->isHingeRight())
|
||||
->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen());
|
||||
}
|
||||
@ -112,7 +112,7 @@ final class BlockStateSerializerHelper{
|
||||
|
||||
public static function encodeFenceGate(FenceGate $block, Writer $out) : Writer{
|
||||
return $out
|
||||
->writeLegacyHorizontalFacing($block->getFacing())
|
||||
->writeCardinalHorizontalFacing($block->getFacing())
|
||||
->writeBool(BlockStateNames::IN_WALL_BIT, $block->isInWall())
|
||||
->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen());
|
||||
}
|
||||
|
@ -103,10 +103,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
|
||||
/** @phpstan-param \Closure(Reader) : Block $c */
|
||||
public function map(string $id, \Closure $c) : void{
|
||||
if(array_key_exists($id, $this->deserializeFuncs)){
|
||||
throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\"");
|
||||
}
|
||||
$this->deserializeFuncs[$id] = $c;
|
||||
$this->simpleCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the existing data deserializer for the given ID, or null if none exists.
|
||||
* This may be useful if you need to override a deserializer, but still want to be able to fall back to the original.
|
||||
*
|
||||
* @phpstan-return ?\Closure(Reader) : Block
|
||||
*/
|
||||
public function getDeserializerForId(string $id) : ?\Closure{
|
||||
return $this->deserializeFuncs[$id] ?? null;
|
||||
}
|
||||
|
||||
/** @phpstan-param \Closure() : Block $getBlock */
|
||||
@ -608,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));
|
||||
@ -647,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));
|
||||
}
|
||||
|
||||
@ -720,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());
|
||||
@ -957,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());
|
||||
@ -1567,6 +1593,10 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
|
||||
return Blocks::SUGARCANE()
|
||||
->setAge($in->readBoundedInt(StateNames::AGE, 0, 15));
|
||||
});
|
||||
$this->mapSlab(Ids::RESIN_BRICK_SLAB, Ids::RESIN_BRICK_DOUBLE_SLAB, fn() => Blocks::RESIN_BRICK_SLAB());
|
||||
$this->mapStairs(Ids::RESIN_BRICK_STAIRS, fn() => Blocks::RESIN_BRICK_STAIRS());
|
||||
$this->map(Ids::RESIN_BRICK_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::RESIN_BRICK_WALL(), $in));
|
||||
$this->map(Ids::RESIN_CLUMP, fn(Reader $in) => Blocks::RESIN_CLUMP()->setFaces($in->readFacingFlags()));
|
||||
$this->mapSlab(Ids::SANDSTONE_SLAB, Ids::SANDSTONE_DOUBLE_SLAB, fn() => Blocks::SANDSTONE_SLAB());
|
||||
$this->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS());
|
||||
$this->map(Ids::SANDSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::SANDSTONE_WALL(), $in));
|
||||
|
@ -37,7 +37,6 @@ use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_key_last;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function get_debug_type;
|
||||
@ -138,8 +137,8 @@ final class BlockStateUpgradeSchemaUtils{
|
||||
|
||||
$convertedRemappedValuesIndex = [];
|
||||
foreach(Utils::stringifyKeys($model->remappedPropertyValuesIndex ?? []) as $mappingKey => $mappingValues){
|
||||
foreach($mappingValues as $k => $oldNew){
|
||||
$convertedRemappedValuesIndex[$mappingKey][$k] = new BlockStateUpgradeSchemaValueRemap(
|
||||
foreach($mappingValues as $oldNew){
|
||||
$convertedRemappedValuesIndex[$mappingKey][] = new BlockStateUpgradeSchemaValueRemap(
|
||||
self::jsonModelToTag($oldNew->old),
|
||||
self::jsonModelToTag($oldNew->new)
|
||||
);
|
||||
@ -361,7 +360,7 @@ final class BlockStateUpgradeSchemaUtils{
|
||||
//remaps with the same number of criteria should be sorted alphabetically, but this is not strictly necessary
|
||||
return json_encode($a->oldState ?? []) <=> json_encode($b->oldState ?? []);
|
||||
});
|
||||
$result->remappedStates[$oldBlockName] = array_values($keyedRemaps);
|
||||
$result->remappedStates[$oldBlockName] = $keyedRemaps; //usort strips keys, so this is already a list
|
||||
}
|
||||
if(isset($result->remappedStates)){
|
||||
ksort($result->remappedStates);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user