mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-09 19:24:12 +00:00
Compare commits
18 Commits
major-next
...
gameplay-p
Author | SHA1 | Date | |
---|---|---|---|
a6042ec7e8 | |||
e494460cfd | |||
2c299e2b49 | |||
ac1e70cd96 | |||
0bbd4af496 | |||
b0bfc30b07 | |||
a91cef37f6 | |||
26afa97cdc | |||
57082c8148 | |||
74ee38ab99 | |||
df069b0418 | |||
ea43fd1917 | |||
ca5d9c3731 | |||
cbcc4d24e0 | |||
223fd74255 | |||
0fef4c6683 | |||
e1ae9a7d69 | |||
10a962daa2 |
19
.github/ISSUE_TEMPLATE/api-change-request.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/api-change-request.md
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
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
|
87
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
87
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -1,87 +0,0 @@
|
||||
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
|
||||
> [!WARNING]
|
||||
> "Latest" is not a valid version.
|
||||
> Failure to fill these fields with valid information may result in your issue being closed.
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
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
Normal file
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
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 -->
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -3,6 +3,9 @@ contact_links:
|
||||
- name: Help & support on Discord
|
||||
url: https://discord.gg/bmSAZBG
|
||||
about: We don't accept support requests on the issue tracker. Please try asking on Discord instead.
|
||||
- name: Help & support on forums
|
||||
url: https://forums.pmmp.io
|
||||
about: We don't accept support requests on the issue tracker. Please try asking on forums instead.
|
||||
- name: Documentation
|
||||
url: https://pmmp.rtfd.io
|
||||
about: PocketMine-MP documentation
|
||||
|
16
.github/ISSUE_TEMPLATE/crash.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/crash.md
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
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
25
.github/ISSUE_TEMPLATE/crash.yml
vendored
@ -1,25 +0,0 @@
|
||||
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
19
.github/ISSUE_TEMPLATE/feature-proposal.yml
vendored
@ -1,19 +0,0 @@
|
||||
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"
|
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
@ -12,10 +12,6 @@ updates:
|
||||
update-types:
|
||||
- "version-update:semver-major"
|
||||
- "version-update:semver-minor"
|
||||
|
||||
#since we lock this to exact versions, it causes conflicts with minor-next & major-next in composer.lock
|
||||
#better to just test updates to this locally anyway since almost every version breaks something
|
||||
- dependency-name: phpstan/phpstan
|
||||
groups:
|
||||
production-patch-updates:
|
||||
dependency-type: production
|
||||
@ -41,7 +37,4 @@ updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
groups:
|
||||
github-actions:
|
||||
patterns: ["*"]
|
||||
interval: weekly
|
||||
|
32
.github/workflows/branch-sync-cron-trigger.yml
vendored
32
.github/workflows/branch-sync-cron-trigger.yml
vendored
@ -1,32 +0,0 @@
|
||||
#Since GitHub automatically disables cron actions after 60 days of repo inactivity, we need the active repo (PM)
|
||||
#to trigger the branch merge workflow explicitly. This avoids the need for TOS-violating actions which we previously
|
||||
#used to keep the restricted action active, as the workflow depends on the activity of this repo anyway.
|
||||
|
||||
name: Trigger branch sync
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" #once per day so we don't spam merge commits on busy days
|
||||
workflow_dispatch: #for testing
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
name: Trigger branch sync RestrictedActions workflow
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Generate access token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
|
||||
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: RestrictedActions
|
||||
|
||||
- name: Dispatch branch sync restricted action
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
repository: ${{ github.repository_owner }}/RestrictedActions
|
||||
event-type: pocketmine_mp_branch_sync
|
43
.github/workflows/build-docker-image.yml
vendored
43
.github/workflows/build-docker-image.yml
vendored
@ -4,16 +4,11 @@ on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release:
|
||||
description: 'Tag name to build'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Update Docker Hub images
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
@ -33,28 +28,16 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Clone pmmp/PocketMine-Docker repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: pmmp/PocketMine-Docker
|
||||
fetch-depth: 1
|
||||
|
||||
|
||||
- name: Get tag name
|
||||
- name: Get tag names
|
||||
id: tag-name
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "release" ]]; then
|
||||
echo TAG_NAME="${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
echo TAG_NAME="${{ github.event.inputs.release }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Unsupported event type: ${{ github.event_name }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Parse version
|
||||
id: version
|
||||
run: |
|
||||
VERSION="${{ steps.tag-name.outputs.TAG_NAME }}"
|
||||
VERSION=$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{')
|
||||
echo TAG_NAME=$VERSION >> $GITHUB_OUTPUT
|
||||
echo MAJOR=$(echo $VERSION | cut -d. -f1) >> $GITHUB_OUTPUT
|
||||
echo MINOR=$(echo $VERSION | cut -d. -f1-2) >> $GITHUB_OUTPUT
|
||||
|
||||
@ -70,7 +53,7 @@ jobs:
|
||||
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build image for tag
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -83,33 +66,33 @@ jobs:
|
||||
|
||||
- name: Build image for major tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
tags: |
|
||||
${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.version.outputs.MAJOR }}
|
||||
ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.version.outputs.MAJOR }}
|
||||
${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MAJOR }}
|
||||
ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MAJOR }}
|
||||
build-args: |
|
||||
PMMP_TAG=${{ steps.tag-name.outputs.TAG_NAME }}
|
||||
PMMP_REPO=${{ github.repository }}
|
||||
|
||||
- name: Build image for minor tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
tags: |
|
||||
${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.version.outputs.MINOR }}
|
||||
ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.version.outputs.MINOR }}
|
||||
${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MINOR }}
|
||||
ghcr.io/${{ steps.docker-repo-name.outputs.NAME }}:${{ steps.tag-name.outputs.MINOR }}
|
||||
build-args: |
|
||||
PMMP_TAG=${{ steps.tag-name.outputs.TAG_NAME }}
|
||||
PMMP_REPO=${{ github.repository }}
|
||||
|
||||
- name: Build image for latest tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
|
47
.github/workflows/copilot-setup-steps.yml
vendored
47
.github/workflows/copilot-setup-steps.yml
vendored
@ -1,47 +0,0 @@
|
||||
name: "Copilot Agent environment setup"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
|
||||
jobs:
|
||||
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
with:
|
||||
php-version: 8.3
|
||||
install-path: "./bin"
|
||||
pm-version-major: 5
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/composer/files
|
||||
~/.cache/composer/vcs
|
||||
key: "composer-v2-cache-8.3-${{ hashFiles('./composer.lock') }}"
|
||||
restore-keys: |
|
||||
composer-v2-cache-
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --prefer-dist --no-interaction
|
||||
|
||||
- name: Clone extension stubs
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: pmmp/phpstorm-stubs
|
||||
path: extension-stubs
|
23
.github/workflows/discord-release-notify.yml
vendored
23
.github/workflows/discord-release-notify.yml
vendored
@ -4,23 +4,18 @@ on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release:
|
||||
description: 'Release to make notification for'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
uses: shivammathur/setup-php@2.31.1
|
||||
with:
|
||||
php-version: 8.3
|
||||
php-version: 8.2
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v4
|
||||
@ -35,17 +30,9 @@ jobs:
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs
|
||||
|
||||
- name: Get tag name
|
||||
- name: Get actual tag name
|
||||
id: tag-name
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "release" ]]; then
|
||||
echo TAG_NAME="${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
echo TAG_NAME="${{ github.event.inputs.release }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Unsupported event type: ${{ github.event_name }}"
|
||||
exit 1
|
||||
fi
|
||||
run: echo TAG_NAME=$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{') >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run webhook post script
|
||||
run: php .github/workflows/discord-release-embed.php ${{ github.repository }} ${{ steps.tag-name.outputs.TAG_NAME }} ${{ github.token }} ${{ secrets.DISCORD_RELEASE_WEBHOOK }} ${{ secrets.DISCORD_NEWS_PING_ROLE_ID }}
|
||||
|
65
.github/workflows/draft-release-from-pr.yml
vendored
Normal file
65
.github/workflows/draft-release-from-pr.yml
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
name: Draft release from PR
|
||||
|
||||
on:
|
||||
#presume that pull_request_target is safe at this point, since the PR was approved and merged
|
||||
#we need write access to prepare the release & create comments
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- stable
|
||||
- minor-next
|
||||
- major-next
|
||||
- "legacy/*"
|
||||
paths:
|
||||
- "src/VersionInfo.php"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check release
|
||||
uses: ./.github/workflows/draft-release-pr-check.yml
|
||||
|
||||
draft:
|
||||
name: Create GitHub draft release
|
||||
needs: [check]
|
||||
if: needs.check.outputs.valid == 'true'
|
||||
|
||||
uses: ./.github/workflows/draft-release.yml
|
||||
|
||||
post-draft-url-comment:
|
||||
name: Post draft release URL as comment
|
||||
needs: [draft]
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Post draft release URL on PR
|
||||
uses: thollander/actions-comment-pull-request@v3
|
||||
with:
|
||||
message: "[Draft release ${{ needs.draft.outputs.version }}](${{ needs.draft.outputs.draft-url }}) has been created for commit ${{ github.sha }}. Please review and publish it."
|
||||
|
||||
trigger-post-release-workflow:
|
||||
name: Trigger post-release RestrictedActions workflow
|
||||
# Not sure if needs is actually needed here
|
||||
needs: [check]
|
||||
if: needs.check.outputs.valid == 'true'
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Generate access token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
|
||||
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: RestrictedActions
|
||||
|
||||
- name: Dispatch post-release restricted action
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
repository: ${{ github.repository_owner }}/RestrictedActions
|
||||
event-type: pocketmine_mp_post_release
|
||||
client-payload: '{"branch": "${{ github.ref }}"}'
|
13
.github/workflows/draft-release-from-tag.yml
vendored
Normal file
13
.github/workflows/draft-release-from-tag.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
#Allows creating a release by pushing a tag
|
||||
#This might be useful for retroactive releases
|
||||
name: Draft release from git tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: "*"
|
||||
|
||||
jobs:
|
||||
draft:
|
||||
name: Create GitHub draft release
|
||||
if: "startsWith(github.event.head_commit.message, 'Release ')"
|
||||
uses: ./.github/workflows/draft-release.yml
|
12
.github/workflows/draft-release-pr-check.yml
vendored
12
.github/workflows/draft-release-pr-check.yml
vendored
@ -24,13 +24,13 @@ permissions:
|
||||
jobs:
|
||||
check-intent:
|
||||
name: Check release trigger
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
outputs:
|
||||
valid: ${{ steps.validate.outputs.DEV_BUILD == 'false' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check IS_DEVELOPMENT_BUILD flag
|
||||
id: validate
|
||||
@ -43,15 +43,15 @@ jobs:
|
||||
#don't do these checks if this isn't a release - we don't want to generate unnecessary failed statuses
|
||||
if: needs.check-intent.outputs.valid == 'true'
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
uses: shivammathur/setup-php@2.31.1
|
||||
with:
|
||||
php-version: 8.3
|
||||
php-version: 8.2
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v4
|
||||
|
126
.github/workflows/draft-release.yml
vendored
126
.github/workflows/draft-release.yml
vendored
@ -1,95 +1,38 @@
|
||||
name: Draft release
|
||||
|
||||
on:
|
||||
#presume that pull_request_target is safe at this point, since the PR was approved and merged
|
||||
#we need write access to prepare the release & create comments
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- stable
|
||||
- minor-next
|
||||
- major-next
|
||||
- "legacy/*"
|
||||
paths:
|
||||
- "src/VersionInfo.php"
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
env:
|
||||
PHP_VERSION: "8.3"
|
||||
workflow_call:
|
||||
outputs:
|
||||
draft-url:
|
||||
description: 'The URL of the draft release'
|
||||
value: ${{ jobs.draft.outputs.draft-url }}
|
||||
version:
|
||||
description: 'PocketMine-MP version'
|
||||
value: ${{ jobs.draft.outputs.version }}
|
||||
|
||||
jobs:
|
||||
skip:
|
||||
name: Check whether to ignore this tag
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
outputs:
|
||||
skip: ${{ steps.exists.outputs.exists == 'true' }}
|
||||
|
||||
steps:
|
||||
- name: Check if release already exists
|
||||
id: exists
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
exists=false
|
||||
if [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||
tag="$(echo "${{ github.ref }}" | cut -d/ -f3-)"
|
||||
if gh release view "$tag" --repo "${{ github.repository }}"; then
|
||||
exists=true
|
||||
fi
|
||||
fi
|
||||
echo exists=$exists >> $GITHUB_OUTPUT
|
||||
|
||||
check:
|
||||
needs: [skip]
|
||||
if: needs.skip.outputs.skip != 'true'
|
||||
name: Check release
|
||||
uses: ./.github/workflows/draft-release-pr-check.yml
|
||||
|
||||
trigger-post-release-workflow:
|
||||
name: Trigger post-release RestrictedActions workflow
|
||||
needs: [check]
|
||||
if: needs.check.outputs.valid == 'true' && github.ref_type != 'tag' #can't do post-commit for a tag
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Generate access token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
|
||||
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: RestrictedActions
|
||||
|
||||
- name: Dispatch 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-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-version: [8.2]
|
||||
|
||||
outputs:
|
||||
draft-url: ${{ steps.create-draft.outputs.html_url }}
|
||||
version: ${{ steps.get-pm-version.outputs.PM_VERSION }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
uses: shivammathur/setup-php@2.31.1
|
||||
with:
|
||||
php-version: ${{ env.PHP_VERSION }}
|
||||
php-version: ${{ matrix.php-version }}
|
||||
|
||||
- name: Restore Composer package cache
|
||||
uses: actions/cache@v4
|
||||
@ -107,7 +50,7 @@ jobs:
|
||||
- name: Calculate build number
|
||||
id: build-number
|
||||
run: |
|
||||
BUILD_NUMBER=$((2300+$GITHUB_RUN_NUMBER)) #to stay above jenkins
|
||||
BUILD_NUMBER=$((2000+$GITHUB_RUN_NUMBER)) #to stay above jenkins
|
||||
echo "Build number: $BUILD_NUMBER"
|
||||
echo BUILD_NUMBER=$BUILD_NUMBER >> $GITHUB_OUTPUT
|
||||
|
||||
@ -120,31 +63,23 @@ jobs:
|
||||
- name: Get PocketMine-MP release version
|
||||
id: get-pm-version
|
||||
run: |
|
||||
PM_VERSION=$(php build/dump-version-info.php base_version)
|
||||
echo PM_VERSION=$PM_VERSION >> $GITHUB_OUTPUT
|
||||
echo PM_VERSION=$(php build/dump-version-info.php base_version) >> $GITHUB_OUTPUT
|
||||
echo PM_MAJOR=$(php build/dump-version-info.php major_version) >> $GITHUB_OUTPUT
|
||||
echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT
|
||||
echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT
|
||||
echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT
|
||||
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-${{ env.PHP_VERSION }}-latest" >> $GITHUB_OUTPUT
|
||||
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate build info
|
||||
run: |
|
||||
php build/generate-build-info-json.php \
|
||||
${{ github.sha }} \
|
||||
${{ steps.get-pm-version.outputs.TAG_NAME }} \
|
||||
${{ steps.get-pm-version.outputs.PM_VERSION }} \
|
||||
${{ github.repository }} \
|
||||
${{ steps.build-number.outputs.BUILD_NUMBER }} \
|
||||
${{ github.run_id }} \
|
||||
@ -165,7 +100,7 @@ jobs:
|
||||
${{ github.workspace }}/core-permissions.rst
|
||||
|
||||
- name: Create draft release
|
||||
uses: ncipollo/release-action@v1.18.0
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
id: create-draft
|
||||
with:
|
||||
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst
|
||||
@ -173,19 +108,12 @@ 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.TAG_NAME }}
|
||||
tag: ${{ steps.get-pm-version.outputs.PM_VERSION }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
skipIfReleaseExists: true #for release PRs, tags will be created on release publish and trigger the tag release workflow - don't create a second draft
|
||||
body: |
|
||||
**For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}**
|
||||
|
||||
Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.CHANGELOG_FILE_NAME }}#${{ steps.get-pm-version.outputs.CHANGELOG_MD_HEADER }}) for details.
|
||||
|
||||
:information_source: Download the recommended PHP binary [here](${{ steps.php-binary-url.outputs.PHP_BINARY_URL }}).
|
||||
|
||||
:warning: Found a bug? Report it on our [issue tracker](${{ github.server_url }}/${{ github.repository }}/issues). **We can't fix bugs if you don't report them.**
|
||||
|
||||
- name: Post draft release URL on PR
|
||||
if: github.event_name == 'pull_request_target'
|
||||
uses: thollander/actions-comment-pull-request@v3
|
||||
with:
|
||||
message: "${{ vars.DRAFT_RELEASE_NOTIFICATION_MENTION }} [Draft release ${{ steps.get-pm-version.outputs.PM_VERSION }}](${{ steps.create-draft.outputs.html_url }}) has been created for commit ${{ github.sha }}. Please review and publish it."
|
||||
|
10
.github/workflows/main-php-matrix.yml
vendored
10
.github/workflows/main-php-matrix.yml
vendored
@ -15,7 +15,7 @@ on:
|
||||
type: number
|
||||
image:
|
||||
description: 'Runner image to use'
|
||||
default: 'ubuntu-22.04'
|
||||
default: 'ubuntu-20.04'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
@ -27,7 +27,7 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
@ -91,7 +91,7 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
@ -125,7 +125,7 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
|
16
.github/workflows/main.yml
vendored
16
.github/workflows/main.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: ["8.3", "8.4"]
|
||||
php: ["8.1", "8.2", "8.3"]
|
||||
|
||||
uses: ./.github/workflows/main-php-matrix.yml
|
||||
with:
|
||||
@ -20,18 +20,18 @@ jobs:
|
||||
|
||||
codestyle:
|
||||
name: Code Style checks
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
uses: shivammathur/setup-php@2.31.1
|
||||
with:
|
||||
php-version: 8.3
|
||||
tools: php-cs-fixer:3.75
|
||||
php-version: 8.2
|
||||
tools: php-cs-fixer:3.49
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@ -40,12 +40,12 @@ jobs:
|
||||
|
||||
shellcheck:
|
||||
name: ShellCheck
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@2.0.0
|
||||
|
32
.github/workflows/pr-remove-waiting-label.yml
vendored
32
.github/workflows/pr-remove-waiting-label.yml
vendored
@ -15,23 +15,19 @@ jobs:
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
async function removeLabel(owner, repo, issue_number, name) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: issue_number,
|
||||
name: name,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
//probably label wasn't set on the issue
|
||||
console.log('Failed to remove label ' + name + ' (probably label isn\'t on the PR): ' + error.message);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
const [owner, repo] = context.payload.repository.full_name.split('/');
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
issue_number: context.payload.number,
|
||||
name: "Status: Waiting on Author",
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
//probably label wasn't set on the issue
|
||||
console.log('Failed to remove label (probably label isn\'t on the PR): ' + error.message);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const [owner, repo] = context.payload.repository.full_name.split('/');
|
||||
removeLabel(owner, repo, context.payload.number, "Status: Waiting on Author");
|
||||
removeLabel(owner, repo, context.payload.number, "Stale");
|
||||
|
29
.github/workflows/pr-stale.yml
vendored
29
.github/workflows/pr-stale.yml
vendored
@ -1,29 +0,0 @@
|
||||
name: 'Clean up stale PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
stale-pr-message: |
|
||||
This PR has been marked as "Waiting on Author", but we haven't seen any activity in 7 days.
|
||||
|
||||
If there is no further activity, it will be closed in 28 days.
|
||||
|
||||
Note for maintainers: Adding an assignee to the PR will prevent it from being marked as stale.
|
||||
|
||||
close-pr-message: |
|
||||
As this PR hasn't been updated for a while, unfortunately we'll have to close it.
|
||||
|
||||
days-before-pr-stale: 7
|
||||
days-before-pr-close: 28
|
||||
only-labels: "Status: Waiting on Author"
|
||||
close-pr-label: "Resolution: Abandoned"
|
||||
exempt-all-assignees: true
|
||||
|
5
.github/workflows/support.yml
vendored
5
.github/workflows/support.yml
vendored
@ -20,7 +20,10 @@ jobs:
|
||||
|
||||
- Check our [Documentation](https://doc.pmmp.io) to see if you can find answers there
|
||||
|
||||
- Ask the community on our [Discord server](https://discord.gg/bmSAZBG)
|
||||
- Ask the community on our [Discord server](https://discord.gg/bmSAZBG) or our [Forums](https://forums.pmmp.io)
|
||||
|
||||
|
||||
[Docs](https://pmmp.rtfd.io) | [Discord](https://discord.gg/bmSAZBG) | [Forums](https://forums.pmmp.io)
|
||||
|
||||
close-issue: true
|
||||
lock-issue: false
|
||||
|
2
.github/workflows/team-pr-auto-approve.yml
vendored
2
.github/workflows/team-pr-auto-approve.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate access token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
|
||||
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
|
||||
|
19
.github/workflows/update-updater-api.yml
vendored
19
.github/workflows/update-updater-api.yml
vendored
@ -4,11 +4,6 @@ on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release:
|
||||
description: 'Release to publish info for'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -19,22 +14,14 @@ jobs:
|
||||
- name: Install jq
|
||||
run: sudo apt update && sudo apt install jq -y
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ github.repository_owner }}/update.pmmp.io
|
||||
ssh-key: ${{ secrets.UPDATE_PMMP_IO_DEPLOY_KEY }}
|
||||
|
||||
- name: Get tag name
|
||||
- name: Get actual tag name
|
||||
id: tag-name
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "release" ]]; then
|
||||
echo TAG_NAME="${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
echo TAG_NAME="${{ github.event.inputs.release }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Unsupported event type: ${{ github.event_name }}"
|
||||
exit 1
|
||||
fi
|
||||
run: echo TAG_NAME=$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{') >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download new release information
|
||||
run: curl -f -L ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ steps.tag-name.outputs.TAG_NAME }}/build_info.json -o new_build_info.json
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +1,6 @@
|
||||
[submodule "tests/plugins/DevTools"]
|
||||
path = tests/plugins/DevTools
|
||||
url = https://github.com/pmmp/DevTools.git
|
||||
[submodule "build/php"]
|
||||
path = build/php
|
||||
url = https://github.com/pmmp/php-build-scripts.git
|
||||
|
@ -5,13 +5,7 @@ $finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__ . '/build')
|
||||
->in(__DIR__ . '/tests')
|
||||
->in(__DIR__ . '/tools')
|
||||
|
||||
//JsonMapper will break if the FQNs in the doc comments for these are shortened :(
|
||||
->notPath('crafting/json')
|
||||
->notPath('inventory/json')
|
||||
->notPath('data/bedrock/block/upgrade/model')
|
||||
->notPath('data/bedrock/item/upgrade/model')
|
||||
|
||||
->notPath('plugins/DevTools')
|
||||
->notName('PocketMine.php');
|
||||
|
||||
return (new PhpCsFixer\Config)
|
||||
|
@ -123,7 +123,7 @@ The following are required as a minimum for pull requests. PRs that don't meet t
|
||||
- Remember, PRs with small diffs are much easier to review. Small PRs are generally reviewed and merged much faster than large ones.
|
||||
- **Start small.** Try fixing minor bugs or doing something isolated (e.g. adding a new block or item) before attempting larger changes.
|
||||
- This helps you get familiar with the codebase, the contribution process, and the expectations of maintainers.
|
||||
- Check out ["Easy task" issues](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue+is%3Aopen+label%3A%22Easy+task%22) on the issues page for something that you could tackle without too much effort.
|
||||
- Check out the [issues page]() for something that you could tackle without too much effort.
|
||||
- **Do not copy-paste other people's code**. Many PRs involve discussion about the changes, and changes are often requested by reviewers. If you don't understand the code you're copy-pasting, your PR is likely to fail.
|
||||
- **Do not edit code directly on github.com.** We recommend learning how to use [`git`](https://git-scm.com). `git` allows you to "clone" a repository onto your computer, so that you can make changes using an IDE.
|
||||
- **Use an IDE, not a text editor.** We recommend PhpStorm or VSCode.
|
||||
|
@ -12,7 +12,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/pmmp/PocketMine-MP/actions/workflows/main.yml"><img src="https://github.com/pmmp/PocketMine-MP/actions/workflows/main.yml/badge.svg" alt="CI" /></a>
|
||||
<a href="https://github.com/pmmp/PocketMine-MP/actions/workflows/main.yml"><img src="https://github.com/pmmp/PocketMine-MP/workflows/CI/badge.svg" alt="CI" /></a>
|
||||
<a href="https://github.com/pmmp/PocketMine-MP/releases/latest"><img alt="GitHub release (latest SemVer)" src="https://img.shields.io/github/v/release/pmmp/PocketMine-MP?label=release&sort=semver"></a>
|
||||
<a href="https://discord.gg/bmSAZBG"><img src="https://img.shields.io/discord/373199722573201408?label=discord&color=7289DA&logo=discord" alt="Discord" /></a>
|
||||
<br>
|
||||
@ -65,8 +65,6 @@ PocketMine-MP accepts community contributions! The following resources will be u
|
||||
* [Building and running PocketMine-MP from source](BUILDING.md)
|
||||
* [Contributing Guidelines](CONTRIBUTING.md)
|
||||
|
||||
New here? Check out [issues with the "Easy task" label](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Easy%20task%22) for things you could work to familiarise yourself with the codebase.
|
||||
|
||||
## Donate
|
||||
PocketMine-MP is free, but it requires a lot of time and effort from unpaid volunteers to develop. Donations enable us to keep delivering support for new versions and adding features your players love.
|
||||
|
||||
|
@ -31,12 +31,12 @@ require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
*/
|
||||
|
||||
/**
|
||||
* @var string[]|Closure[] $options
|
||||
* @phpstan-var array<string, string|Closure() : string> $options
|
||||
* @var string[]|\Closure[] $options
|
||||
* @phpstan-var array<string, string|\Closure() : string> $options
|
||||
*/
|
||||
$options = [
|
||||
"base_version" => VersionInfo::BASE_VERSION,
|
||||
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION, limit: 2)[0],
|
||||
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
|
||||
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
|
||||
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
|
||||
"changelog_file_name" => function() : string{
|
||||
|
@ -28,7 +28,6 @@ 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;
|
||||
@ -60,7 +59,7 @@ foreach($files as $file){
|
||||
continue;
|
||||
}
|
||||
$path = Path::join(BEDROCK_DATA_PATH, $file);
|
||||
if(!is_file($path) && !is_dir($path)){
|
||||
if(!is_file($path)){
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -68,7 +67,6 @@ foreach($files as $file){
|
||||
'README.md',
|
||||
'LICENSE',
|
||||
'composer.json',
|
||||
'.github'
|
||||
] as $ignored){
|
||||
if($file === $ignored){
|
||||
continue 2;
|
||||
|
174
build/make-release.php
Normal file
174
build/make-release.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?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: 1d9cda6688...5016e0a3d5
@ -25,7 +25,6 @@ namespace pocketmine\server_phar_stub;
|
||||
|
||||
use function clearstatcache;
|
||||
use function copy;
|
||||
use function define;
|
||||
use function fclose;
|
||||
use function fflush;
|
||||
use function flock;
|
||||
@ -166,5 +165,4 @@ $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);
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
# 5.22.0
|
||||
Released 4th December 2024.
|
||||
|
||||
**For Minecraft: Bedrock Edition 1.21.50**
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.50.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.21.50.
|
||||
- Removed support for earlier versions.
|
@ -1,139 +0,0 @@
|
||||
# 5.23.0
|
||||
Released 5th December 2024.
|
||||
|
||||
This is a minor feature release, including new gameplay features, internals improvements, API additions and
|
||||
deprecations, and improvements to timings.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## General
|
||||
- `/timings` now supports collecting timings from async task workers. These new timings will be shown alongside `Full Server Tick` timings, but will not be counted in total load.
|
||||
- Added `/xp` command.
|
||||
- `start.sh` will now emit warnings when the server process exits with an unusual exit code. This helps to detect unexpected segmentation faults and other kinds of native errors.
|
||||
|
||||
## Gameplay
|
||||
- Added the following new items:
|
||||
- End Crystal
|
||||
- Goat Horn (all variants)
|
||||
- Ice Bomb (from Education Edition)
|
||||
- Recovery Compass
|
||||
- Added the following enchantments:
|
||||
- Frost Walker
|
||||
- Sugarcane now self-destructs when there is no water adjacent to the base block.
|
||||
- Added basic support for middle-clicking on entities to get their spawn eggs.
|
||||
- Added sounds when drinking potions.
|
||||
- Eating food is now allowed in creative mode and in peaceful difficulty.
|
||||
|
||||
## API
|
||||
### `pocketmine\block`
|
||||
- Extracted `MultiAnyFacingTrait` and `MultiAnySupportTrait` from `GlowLichen` to enable reuse in other blocks.
|
||||
- The following API methods have been deprecated:
|
||||
- `Campfire->getInventory()` - this was added by mistake and can't be well-supported given the way that blocks work
|
||||
|
||||
### `pocketmine\command`
|
||||
- The following classes have been added:
|
||||
- `ClosureCommand` - allows registering a closure to execute a command
|
||||
|
||||
### `pocketmine\event`
|
||||
- Added APIs to `PlayerInteractEvent` to allow toggling item and block interactions.
|
||||
- This allows various customisations, such as allowing interactions when sneaking, selectively disabling item or block reactions, etc.
|
||||
- If both item and block interactions are disabled, the event is **not** cancelled (blocks can still be placed).
|
||||
- The following API methods have been added:
|
||||
- `public PlayerInteractEvent->setUseBlock(bool $useBlock) : void`
|
||||
- `public PlayerInteractEvent->setUseItem(bool $useItem) : void`
|
||||
- `public PlayerInteractEvent->useBlock() : bool` - returns whether the block can respond to the interaction (toggling levers, opening/closing doors, etc).
|
||||
- `public PlayerInteractEvent->useItem() : bool` - returns whether the item can respond to the interaction (spawn eggs, flint & steel, etc).
|
||||
- The following new classes have been added:
|
||||
- `player\PlayerEntityPickEvent` - called when a player middle-clicks on an entity
|
||||
|
||||
### `pocketmine\inventory\transaction`
|
||||
- The following API methods have been deprecated:
|
||||
- `InventoryAction->onAddToTransaction()`
|
||||
|
||||
### `pocketmine\permission`
|
||||
- The following API methods have been deprecated:
|
||||
- `PermissionManager->getPermissionSubscriptions()`
|
||||
- `PermissionManager->subscribeToPermission()`
|
||||
- `PermissionManager->unsubscribeFromAllPermissions()`
|
||||
- `PermissionManager->unsubscribeFromPermission()`
|
||||
|
||||
### `pocketmine\plugin`
|
||||
- The following classes have been deprecated:
|
||||
- `DiskResourceProvider`
|
||||
- `ResourceProvider`
|
||||
|
||||
### `pocketmine\promise`
|
||||
- `Promise::all()` now accepts zero promises. This will return an already-resolved promise with an empty array.
|
||||
|
||||
### `pocketmine\scheduler`
|
||||
- Added PHPStan generic types to `TaskHandler` and related APIs in `TaskScheduler` and `Task`.
|
||||
- The following API methods have been deprecated
|
||||
- `AsyncTask->publishProgress()`
|
||||
- `AsyncTask->onProgressUpdate()`
|
||||
|
||||
### `pocketmine\timings`
|
||||
- Timings can now notify other code when timings are enabled/disabled, reloaded, or collected.
|
||||
- The intent of this is to facilitate timings usage on other threads, and have the results collected into a single timings report.
|
||||
- Timings cannot directly control timings on other threads, so these callbacks allow plugins to use custom mechanisms to toggle, reset and collect timings.
|
||||
- PocketMine-MP currently uses this to collect timings from async task workers. More internal threads may be supported in the future.
|
||||
- The following API methods have been added:
|
||||
- `public static TimingsHandler::getCollectCallbacks() : ObjectSet<\Closure() : list<Promise<list<string>>>` - callbacks for (asynchronously) collecting timings (typically from other threads). The returned promises should be resolved with the result of `TimingsHandler::printCurrentThreadRecords()`.
|
||||
- `public static TimingsHandler::getReloadCallbacks() : ObjectSet<\Closure() : void>` - callbacks called when timings are reset
|
||||
- `public static TimingsHandler::getToggleCallbacks() : ObjectSet<\Closure(bool $enable) : void>` - callbacks called when timings are enabled/disabled
|
||||
- `public static TimingsHandler::requestPrintTimings() : Promise<list<string>>` - asynchronously collects timing results from all threads and assembles them into a single report
|
||||
- The following API methods have been deprecated:
|
||||
- `TimingsHandler::printTimings()` - this function cannot support async timings collection. Use `TimingsHandler::requestPrintTimings()` instead.
|
||||
- `Timings::getAsyncTaskErrorTimings()` - internal method that is no longer needed
|
||||
- The following constants have been deprecated:
|
||||
- `Timings::GROUP_BREAKDOWN` - no longer used
|
||||
|
||||
### `pocketmine\utils`
|
||||
- The following API methods have been added:
|
||||
- `public static Utils::getRandomFloat() : float` - returns a random float between 0 and 1. Drop-in replacement for `lcg_value()` in PHP 8.4.
|
||||
|
||||
## Internals
|
||||
- Blocks are now always synced with the client during a right-click-block interaction. This clears mispredictions on the client in case the new `PlayerInteractEvent` flags were customized by plugins.
|
||||
- `VanillaBlocks` and `VanillaItems` now use reflection to lookup TypeId constants by registration name, instead of requiring TypeIds to be manually specified.
|
||||
- While this is obviously a hack, it prevents incorrect constants from being used when adding new blocks, and guarantees that the names of constants in `BlockTypeIds` and `ItemTypeIds` will match their corresponding entries in `VanillaBlocks` and `VanillaItems` respectively.
|
||||
- It also significantly improves readability of `VanillaBlocks` and `VanillaItems`, as well as eliminating ugly code like `WoodLikeBlockIdHelper`.
|
||||
- In PM6, the team is exploring options to redesign `VanillaBlocks` and `VanillaItems` to eliminate the need for defining separate TypeIds entirely.
|
||||
- `ConsoleReader` now uses socket support in `proc_open()` to transmit IPC messages to the server process. Previously, a temporary socket server was used, which was unreliable in some conditions.
|
||||
- Event handler tests have been converted to PHPUnit tests by mocking `Server` and `Plugin` instances. Previously, these required integration tests for these dependencies.
|
||||
- Fixed various deprecation warnings in PHP 8.4.
|
||||
- `netresearch/jsonmapper` is now used at `5.0.0`. The PMMP fork of this library has been removed, as it is no longer needed.
|
||||
|
||||
# 5.23.1
|
||||
Released 5th December 2024.
|
||||
|
||||
## Fixes
|
||||
- Fixed signs not creating a tile when placed.
|
||||
|
||||
## Internals
|
||||
- Improved blockstate consistency check to detect tiles disappearing during refactors.
|
||||
|
||||
# 5.23.2
|
||||
Released 9th December 2024.
|
||||
|
||||
## General
|
||||
- Updated translations for Russian and Korean.
|
||||
|
||||
## Fixes
|
||||
- Fixed server build number.
|
||||
- Fixed some crashes being misreported as plugin-involved.
|
||||
|
||||
## Internals
|
||||
- Removed legacy `build/make-release.php` script. This script is no longer used, as all releases should now follow the PR workflow.
|
||||
|
||||
# 5.23.3
|
||||
Released 22nd January 2025.
|
||||
|
||||
## Fixes
|
||||
- Fixed crashes with PHP internal stack frames being flagged as plugin crashes.
|
||||
- Fixed note block instrument sounds in 1.21.50.
|
||||
|
||||
## Internals
|
||||
- Updated GitHub issue templates to use issue forms.
|
@ -1,108 +0,0 @@
|
||||
# 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.
|
@ -1,52 +0,0 @@
|
||||
# 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.
|
||||
|
||||
# 5.25.1
|
||||
Released 26th February 2025.
|
||||
|
||||
## Fixes
|
||||
- Fixed confusing exception message when a block-breaking tool has an efficiency value of zero.
|
||||
- Fixed incorrect facing of doors since 1.21.60 (resulted in mismatched AABBs between client & server, rendering glitches etc.)
|
||||
- Resource pack UUIDs are now validated on load. Previously, invalid UUIDs would be accepted, and potentially cause a server crash on player join.
|
||||
|
||||
# 5.25.2
|
||||
Released 4th March 2025.
|
||||
|
||||
## Fixes
|
||||
- Added limits to various `explode()` calls.
|
@ -1,71 +0,0 @@
|
||||
# 5.26.0
|
||||
Released 22nd March 2025.
|
||||
|
||||
This is a minor feature release focused on performance improvements.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## Performance
|
||||
- Significantly improved performance of entity movement. Load testing with item entities showed a 3x increase in the number of entities supported without lag.
|
||||
- Significantly improved performance of on-ground checks for player movement. This still needs further work, but optimisations implemented in this version should improve performance substantially.
|
||||
- Updated `pocketmine/nbt` dependency with performance improvements to `TAG_Compound` and `TAG_List` comparison. This should improve performance of inventory-related actions.
|
||||
- `InventoryTransaction` now avoids useless item clones when processing transactions, which should improve performance of inventory-related actions.
|
||||
|
||||
## Dependencies
|
||||
- `pocketmine/bedrock-protocol` has been updated to `36.2.0`, which adds new functions to access some packet fields.
|
||||
- `pocketmine/nbt` has been updated to `1.1.0`, which improves performance when comparing NBT object trees.
|
||||
|
||||
## Gameplay
|
||||
- Block breaking animation speed now takes into account the following: jumping, being in water, haste, mining fatigue
|
||||
|
||||
## Tools
|
||||
- `blockstate-upgrade-schema-utils.php` now has a new `dump-table` command, which turns a `.bin` palette table file into human-readable text for debugging.
|
||||
|
||||
## API
|
||||
### `pocketmine\block`
|
||||
- The following methods have been added:
|
||||
- `public RuntimeBlockStateRegistry->hasStateId(int $stateId) : bool` - checks whether the given state ID is registered
|
||||
|
||||
### `pocketmine\crafting`
|
||||
- The following methods have been deprecated:
|
||||
- `CraftingManager::sort()` - this was implicitly internal anyway
|
||||
|
||||
### `pocketmine\utils`
|
||||
- The following constants have been added:
|
||||
- `TextFormat::MATERIAL_RESIN`
|
||||
- The following static properties have been added:
|
||||
- `Terminal::$COLOR_MATERIAL_RESIN`
|
||||
|
||||
### `pocketmine\data\bedrock\block`
|
||||
- `BlockStateToObjectDeserializer` now permits overriding **deserializers** for Bedrock IDs. This may be useful to implement custom state handling, or to implement missing block variants (such as snow cauldron).
|
||||
- This was originally prohibited since 5.0.0. However, there is no technical reason to disallow overriding **deserializers**.
|
||||
- Overriding **serializers** is still **not permitted**. Reusing type IDs doesn't make any sense and would break internal design contracts.
|
||||
- If you want to make a custom version of a vanilla block, create a custom type ID for it, exactly as you would for a regular custom block.
|
||||
- The following methods have been added:
|
||||
- `public BlockStateToObjectDeserializer->getDeserializerForId(string $id) : ?(\Closure(BlockStateReader) : Block)`
|
||||
|
||||
### `pocketmine\data\bedrock\item`
|
||||
- `ItemDeserializer` now permits overriding **deserializers** for Bedrock IDs. As above, this may be useful to implement custom data handling, or to implement missing variants of existing items.
|
||||
- This was originally prohibited since 5.0.0. However, there is no technical reason to disallow overriding **deserializers**.
|
||||
- Overriding **serializers** is still **not permitted**. Reusing type IDs doesn't make any sense and would break internal design contracts.
|
||||
- As above, if you want to make a custom version of a vanilla item, create a custom type ID for it, exactly as you would for a regular custom item.
|
||||
- The following methods have been added:
|
||||
- `public ItemDeserializer->getDeserializerForId(string $id) : ?(\Closure(SavedItemData) : Item)`
|
||||
|
||||
## Internals
|
||||
- `new $class` is now banned on new internals code by a PHPStan rule. Closures or factory objects should be used instead for greater flexibility and better static analysis.
|
||||
- `CraftingManager` now uses a more stable hash function for recipe output filtering.
|
||||
- `ChunkCache` now accepts `int $dimensionId` in the constructor. This may be useful for plugins which implement the nether.
|
||||
- `RuntimeBlockStateRegistry` now precomputes basic collision info about known states for fast paths.
|
||||
- This permits specialization for common shapes like cubes and collisionless blocks, which allows skipping complex logic in entity movement calculation. This vastly improves performance.
|
||||
- Any block whose class overrides `readStateFromWorld()` or `getModelPositionOffset()` will *not* be optimised.
|
||||
- `Block->recalculateCollisionBoxes()` now has a hard requirement not to depend on anything other than available properties. It must not use `World` or its position.
|
||||
- This change was problematic for `ChorusPlant`, which used nearby blocks to calculate its collision boxes.
|
||||
- Blocks which need nearby blocks should override `readStateFromWorld()` and set dynamic state properties, similar to fences.
|
||||
- This design flaw will be corrected with a major change to `Block` internals currently in planning for a future major version.
|
||||
- `Block->getCollisionBoxes()` may not be called at all during gameplay for blocks with shapes determined to be simple, like cubes and collisionless blocks.
|
||||
- `BlockStateToObjectDeserializer` now checks if the returned blockstate is registered in `RuntimeBlockStateRegistry` to promote earlier error detection (instead of crashing in random code paths).
|
@ -1,24 +0,0 @@
|
||||
# 5.27.0
|
||||
Released 27th March 2025.
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.70.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## Interim releases
|
||||
If you're upgrading from 5.25.x directly to 5.27.0, please also read the following changelogs, as the interim releases contain important changes:
|
||||
- [5.26.0](https://github.com/pmmp/PocketMine-MP/blob/5.26.0/changelogs/5.26.md#5260) - Performance improvements and other internal improvements
|
||||
|
||||
## General
|
||||
- Aded support for Minecraft: Bedrock Edition 1.21.70.
|
||||
- Removed support for earlier versions.
|
||||
|
||||
# 5.27.1
|
||||
Released 6th April 2025.
|
||||
|
||||
## Fixes
|
||||
- Updated RakLib to get ping timestamp handling fixes.
|
@ -1,34 +0,0 @@
|
||||
# 5.28.0
|
||||
Released 9th May 2025.
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.80.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.21.80.
|
||||
- Removed support for earlier versions.
|
||||
|
||||
## Fixes
|
||||
- `AvailableEnchantmentRegistry` now requires provided tags to always be `string`. Previously, this wasn't enforced, leading to random crashes in core code related to enchanting.
|
||||
- `Entity->setFireTicks()` and `Entity->setOnFire()` now truncate the fire time to the max value instead of throwing exceptions.
|
||||
|
||||
## Internals
|
||||
- Improved PHPStan error reporting for unsafe foreaches. Foreach on an array with implicit keys now generates different errors than foreach on an array with string keys.
|
||||
|
||||
# 5.28.1
|
||||
Released 17th May 2025.
|
||||
|
||||
## Fixes
|
||||
- Fixed errors when PlayStation players attempt to join due to null `TitleID`.
|
||||
|
||||
# 5.28.2
|
||||
Released 17th May 2025.
|
||||
|
||||
## Fixes
|
||||
- Fixed version constraints which were incorrectly updated during the 1.21.80 update. This led to an unnoticed failure to update BedrockProtocol in the previous patch release.
|
||||
- Actually fixed PlayStation issues this time
|
@ -1,25 +0,0 @@
|
||||
# 5.29.0
|
||||
Released 18th June 2025.
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.90.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.21.90.
|
||||
- Removed support for earlier versions.
|
||||
|
||||
## Fixes
|
||||
- Fixed thread crashes sometimes not reporting proper cause information in crashdumps.
|
||||
- Fixed crash when a plugin replaced a player's held tool with a different tool with a damage exceeding the old tool's max damage during an action.
|
||||
- Fixed performance issue of `PlayerAuthInputPacket` input flags handling (broken change detection).
|
||||
- Fixed `BaseInventory->addItem()` triggering updates on empty slots when no items were added.
|
||||
- Fixed slow check in `SubChunk` block layer garbage collection.
|
||||
|
||||
## Internals
|
||||
- `LoginPacketHandler->processLogin()` signature has changed. This will break any plugins overriding `LoginPacketHandler`. As noted above, this is _not_ covered by the API version guarantee.
|
||||
- Automated branch sync for `minor-next` and `major-next` is now triggered by `repository_dispatch` from a cron job in this repository instead of `RestrictedActions`. The `RestrictedActions` cron job was getting automatically disabled by GitHub due to repo inactivity.
|
@ -1,73 +0,0 @@
|
||||
# 5.30.0
|
||||
Released 18th June 2025.
|
||||
|
||||
This is a minor feature release containing API additions, internals cleanup and user experience improvements.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## General
|
||||
- Significantly reduced log spam when unknown blocks, tiles and entities are found in saved worlds.
|
||||
- The file name structure for crashdumps has been changed to improve sorting order in file browsers.
|
||||
- Buffering is now skipped on the RakLib layer. In theory this could reduce player network latency by 10 ms (YMMV).
|
||||
|
||||
## Gameplay
|
||||
### Blocks
|
||||
- Many blocks have had their hardness and blast resistance updated to match vanilla.
|
||||
- Implemented Respawn Anchor.
|
||||
- Melon Stem and Pumpkin Stem drop amounts should now match vanilla (using binomial distribution).
|
||||
|
||||
## API
|
||||
## General
|
||||
- Verification of save registration has been added for blocks, entities and tiles. This is intended to make it easier to find mistakes when registering custom things, which previously would produce obscure core crashes.
|
||||
|
||||
### `pocketmine\event\block`
|
||||
- The following classes have been added:
|
||||
- `BlockPreExplodeEvent` - called before a block tries to explode
|
||||
- `BlockExplodeEvent` - called when after a block's explosion calculation has been done, but before any changes are applied
|
||||
|
||||
### `pocketmine\event\entity`
|
||||
- The following classes have been added:
|
||||
- `EntityExtinguishEvent` - called when a burning entity is extinguished by water or other sources
|
||||
- `EntityFrostWalkerEvent` - called every tick upon which an entity wearing Frost Walker boots moves; this can be used to customise or cancel the behaviour of the Frost Walker enchantment
|
||||
|
||||
### `pocketmine\event\player`
|
||||
- The following classes have been added:
|
||||
- `PlayerRespawnAnchorUseEvent` - called when a player interacts with a charged respawn anchor
|
||||
|
||||
### `pocketmine\entity`
|
||||
- The following methods have been added:
|
||||
- `public Entity->getStepHeight() : float`
|
||||
- `public Entity->setStepHeight(float $stepHeight) : void`
|
||||
|
||||
### `pocketmine\world\generator`
|
||||
- Generator execution has been decoupled from `PopulationTask` and async tasks in general. The following classes have been added:
|
||||
- `executor\GeneratorExecutor`
|
||||
- `executor\SyncGeneratorExecutor` - runs a generator on the main thread (used for flat world generation, which doesn't need threads)
|
||||
- `executor\AsyncGeneratorExecutor` - runs a generator inside an async task, as before
|
||||
- `PopulationUtils` - contains population business logic previously baked into `PopulationTask` - this permits the reuse of that logic outside async tasks
|
||||
- The following methods have signature changes:
|
||||
- `GeneratorManager->addGenerator()` now accepts an optional `bool $fast` parameter, defaulting to `false`; setting this to `true` will cause your generator to run on the main thread
|
||||
- The following methods have been added:
|
||||
- `public GeneratorManagerEntry->isFast() : bool` - returns whether this generator should run on the main thread
|
||||
- `PopulationTask` has been marked as `@internal`. In the next major version, it will move to the `generator\executor` namespace; however, for now it stays put because plugins currently have no other way to regenerate chunks.
|
||||
|
||||
## Internals
|
||||
- World data version numbers have been consolidated in `pocketmine\data\bedrock\WorldDataVersions`. This removes the need to modify several different files to support new world versions, and reduces the chances of things getting missed.
|
||||
- Block hardness and blast resistance is now unit-tested against `block_properties_table.json` in `BedrockData`. This file comes from vanilla BDS, so we can use it to verify compliance.
|
||||
- Protocol-layer "server auth block breaking" has been enabled. Functionally, this is no different from the previous system, it just works differently on the network layer.
|
||||
- Various internal classes in the `pocketmine\world\generator` namespace have been moved to the `generator\executor` namespace.
|
||||
- Removed `World->registerGenerator()` and `World->unregisterGenerator()`.
|
||||
- Removed redundant calls to `curl_close()` (obsolete since PHP 8.0).
|
||||
|
||||
# 5.30.1
|
||||
Released 23rd June 2025.
|
||||
|
||||
## Fixes
|
||||
- Fixed accidental break of backwards compatibility in `EntityExplodeEvent` introduced in the previous release.
|
||||
- Fixed placement of player holding block when exploding respawn anchor.
|
||||
- Updated BedrockProtocol to fix incorrect encoding of `ServerScriptDebugDrawerPacket`.
|
||||
- Disabled client-side locator bar, allowing plugins to write their own implementations.
|
@ -1,14 +0,0 @@
|
||||
# 5.31.0
|
||||
Released 8th July 2025.
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.93.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.21.93.
|
||||
- Removed support for earlier versions.
|
@ -1,25 +0,0 @@
|
||||
# 5.32.0
|
||||
Released 6th August 2025.
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.100.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.21.100.
|
||||
- Removed support for earlier versions.
|
||||
|
||||
## Fixes
|
||||
- Fixed deadlock on RakLib thread crash (e.g. due to port binding failure).
|
||||
|
||||
# 5.32.1
|
||||
Released 14th August 2025.
|
||||
|
||||
## Fixes
|
||||
- Hardened checks when processing resource pack sending during player logins.
|
||||
- Fixed content log warning about crafting recipe with missing ID.
|
||||
- Fixed packets in a batch still being processed after one of them caused the session to be terminated.
|
@ -1,135 +0,0 @@
|
||||
# 5.33.0
|
||||
Released 30th August 2025.
|
||||
|
||||
This is a minor feature release containing internals improvements, API improvements and new gameplay features.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## Performance
|
||||
- Worlds now remember when a chunk isn't generated. This reduces world I/O during world generation.
|
||||
- `BlockObjectToStateSerializer` now creates fewer objects in certain cases.
|
||||
|
||||
## Gameplay
|
||||
- The following blocks have been added and/or are now properly supported:
|
||||
- Hanging signs
|
||||
- Illager banners
|
||||
|
||||
## Tools
|
||||
- `generate-bedrock-data-from-packets.php` now represents items as strings directly when only an ID is present. This significantly improves readability in `BedrockData` and reduces file sizes.
|
||||
|
||||
## API
|
||||
### `pocketmine\block`
|
||||
- Added (and implemented) interfaces for many common block properties, to allow `instanceof` to be used:
|
||||
- `Ageable`: for blocks with age, such as crops
|
||||
- `AnyFacing`: for blocks which can face up, down, and horizontal directions (not the same as `HorizontalFacing`!)
|
||||
- `Colored`: for blocks with 16 `DyeColor` variants
|
||||
- `CoralMaterial`: for coral blocks, provides access to coral type and dead/alive
|
||||
- `HorizontalFacing`: for blocks which can **only** face horizontal directions (not the same as `AnyFacing`!)
|
||||
- `Lightable`: for light-source blocks which can be turned on and off, e.g. redstone lamp
|
||||
- `MultiAnyFacing`: for blocks which can appear in multiple faces of the same block (including up, down, and horizontal faces), e.g. glow lichen
|
||||
- `PillarRotation`: for blocks which can be oriented on an axis, e.g. logs
|
||||
- `PoweredByRedstone`: for blocks which receive power from a redstone component, e.g. redstone lamp
|
||||
- `SignLikeRotation`: for blocks which can be rotated 16 ways, e.g. signs, banners
|
||||
- `WoodMaterial`: for blocks made from wood
|
||||
- These interfaces have been implemented on many blocks. For the sake of brevity, they are not listed here, but you can expect to see them wherever the corresponding traits were used.
|
||||
- The following classes have been added:
|
||||
- `BaseOminousBanner`
|
||||
- `CeilingCenterHangingSign` - both chains connected to the same point on the block above, can face 16 directions
|
||||
- `CeilingEdgesHangingSign` - each chain connected to separate edges of the block above, can face 4 directions
|
||||
- `OminousFloorBanner` - floor version of illager banner, can face 16 directions
|
||||
- `OminousWallBanner` - wall version of illager banner, can face 4 directions
|
||||
- `WallHangingSign` - hangs from a horizontal beam, can face 4 directions
|
||||
- The following API methods have been added:
|
||||
- `public ChiseledBookshelf->setSlots(list<ChiseledBookshelfSlot> $slots) : $this`
|
||||
- `public static VanillaBlocks` methods:
|
||||
- `ACACIA_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `ACACIA_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `ACACIA_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `BIRCH_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `BIRCH_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `BIRCH_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `CHERRY_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `CHERRY_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `CHERRY_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `CRIMSON_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `CRIMSON_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `CRIMSON_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `DARK_OAK_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `DARK_OAK_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `DARK_OAK_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `JUNGLE_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `JUNGLE_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `JUNGLE_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `MANGROVE_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `MANGROVE_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `MANGROVE_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `OAK_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `OAK_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `OAK_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `OMINOUS_FLOOR_BANNER() : OminousFloorBanner`
|
||||
- `OMINOUS_WALL_BANNER() : OminousWallBanner`
|
||||
- `PALE_OAK_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `PALE_OAK_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `PALE_OAK_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `SPRUCE_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `SPRUCE_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `SPRUCE_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `WARPED_CEILING_CENTER_HANGING_SIGN() : CeilingCenterHangingSign`
|
||||
- `WARPED_CEILING_EDGES_HANGING_SIGN() : CeilingEdgesHangingSign`
|
||||
- `WARPED_WALL_HANGING_SIGN() : WallHangingSign`
|
||||
- `public AgeableTrait->getMaxAge() : int` (included by all growable plant-like blocks, e.g. crops)
|
||||
|
||||
### `pocketmine\data\bedrock\block\convert`
|
||||
- A new system for symmetric block serializers and deserializers has been introduced.
|
||||
- This allows registering both a serializer and a deserializer with the same code, meaning way less code
|
||||
- It also eliminates information duplication and potential inconsistencies, improving maintainability.
|
||||
- A proper way to deal with flattened IDs (e.g. color blocks) has been introduced which _doesn't_ require hardcoding a giant mess of IDs
|
||||
- This symmetric system covers 99% of blocks which have a 1:1 association between PM and vanilla blocks, or 1:N where IDs are flattened
|
||||
- However, there are still some special cases which require registering separate serializers and deserializers (usually in cases where the PM implementation deviates from Mojang where Mojang's implementation sucks, such as hanging signs or big dripleaf).
|
||||
- No backwards compatibility breaks are expected as a result of this change. However, it's recommended to migrate old code to this new system for maintainability.
|
||||
- The following new classes have been added:
|
||||
- `BlockSerializerDeserializerRegistrar` - handles unified registration of block serializers and deserializers, based on a provided block model
|
||||
- `FlattenedIdModel` - represents a block with some properties baked into its Minecraft ID, e.g. coral or color blocks
|
||||
- `Model` - represents a regular block with all properties in its `states` NBT
|
||||
- `property\BoolFromStringProperty<TBlock>` - property mapping a bool value from a string NBT state
|
||||
- `property\BoolProperty<TBlock>`
|
||||
- `property\CommonProperties` - singleton containing commonly-used block property definitions and groups, e.g. facing, stair properties
|
||||
- `property\EnumFromRawStateMap<TEnum, TRaw>` - maps a raw NBT value to a PHP `enum` and vice versa
|
||||
- `property\IntFromRawStateMap<TRaw>` - maps a raw NBT value to PM integer constants and vice versa
|
||||
- `property\IntProperty<TBlock>` - an integer range property with a min, max, and optional offset
|
||||
- `property\Property<TBlock>` - interface implemented by all property definitions accepted by a `Model` or `FlattenedIdModel`
|
||||
- `property\StateMap<TValue, TRaw>` - interface implemented by classes accepted by mapping properties, e.g. `BoolFromStringProperty`
|
||||
- `property\StringProperty<TBlock>` - interface implemented by properties whose raw outputs are strings - these can be used as ID components in `FlattenedIdModel`
|
||||
- `property\ValueFromIntProperty<TBlock, TValue>` - property mapping a generic PM value from an int NBT state
|
||||
- `property\ValueFromStringProperty<TBlock, TValue>` - same as above, but for a string NBT state
|
||||
- `property\ValueSetFromIntProperty<TBlock, TOption>` - a property mapping an `int[]` or `enum[]` from a set of flags in NBT states
|
||||
- `property\ValueMappings` - singleton containing commonly-needed `StateMap`s
|
||||
- The following classes have been deprecated:
|
||||
- `BlockStateDeserializerHelper`
|
||||
- `BlockStateSerializerHelper`
|
||||
- The following methods have been deprecated:
|
||||
- All methods for decoding mapped property types in `BlockStateReader`, e.g. `readFacingDirection()`
|
||||
- All methods for encoding mapped property types in `BlockStateWriter`, e.g. `writeFacingDirection()`
|
||||
- All specific blocktype mapping functions in `BlockStateToObjectDeserializer`, e.g. `mapStairs()`
|
||||
- All specific blocktype mapping functions in `BlockObjectToStateSerializer`, e.g. `mapStairs()`
|
||||
|
||||
### `pocketmine\item`
|
||||
- The following hooks have been added:
|
||||
- `public Item->getPlacementTransaction(Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : ?BlockTransaction` - allows more complex logic for itemblocks to place blocks, without duplicating their placement conditions (used for hanging signs)
|
||||
|
||||
### `pocketmine\world`
|
||||
- `World->setChunk()` now verifies that blockstate IDs in the provided chunk are all registered in `RuntimeBlockStateRegistry`. This should provide earlier detection for custom block registration errors by plugins.
|
||||
|
||||
## Internals
|
||||
- `BlockStateUpgrader` is now almost entirely independent from `BlockStateData`. It's anticipated that the upgrader library will be separable from the core in the future.
|
||||
- `Block->readStateFromWorld()` is now triggered on chunk load for any position containing a tile. This should allow more effective updating of blocks with properties in their tiles.
|
||||
|
||||
# 5.33.1
|
||||
Released 31st August 2025.
|
||||
|
||||
## Fixes
|
||||
- Fixed banners placed in prior versions getting their tiles deleted (due to missing `Type` tags).
|
@ -5,7 +5,7 @@
|
||||
"homepage": "https://pmmp.io",
|
||||
"license": "LGPL-3.0",
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"php": "^8.1",
|
||||
"php-64bit": "*",
|
||||
"ext-chunkutils2": "^0.3.1",
|
||||
"ext-crypto": "^0.3.1",
|
||||
@ -32,34 +32,30 @@
|
||||
"ext-zlib": ">=1.2.11",
|
||||
"composer-runtime-api": "^2.0",
|
||||
"adhocore/json-comment": "~1.2.0",
|
||||
"netresearch/jsonmapper": "~v5.0.0",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-data": "~6.0.0+bedrock-1.21.100",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.15.0+bedrock-1.21.100",
|
||||
"pocketmine/bedrock-protocol": "~40.0.0+bedrock-1.21.100",
|
||||
"pocketmine/netresearch-jsonmapper": "~v4.4.999",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-data": "~2.14.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.13.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.40",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "dev-rewrite",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
"pocketmine/errorhandler": "^0.7.0",
|
||||
"pocketmine/locale-data": "~2.25.0",
|
||||
"pocketmine/locale-data": "~2.22.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/math": "dev-major-next as 1.0.0",
|
||||
"pocketmine/nbt": "~1.1.0",
|
||||
"pocketmine/raklib": "~1.2.0",
|
||||
"pocketmine/math": "~1.0.0",
|
||||
"pocketmine/nbt": "~1.0.0",
|
||||
"pocketmine/raklib": "~1.1.0",
|
||||
"pocketmine/raklib-ipc": "~1.0.0",
|
||||
"pocketmine/snooze": "^0.5.0",
|
||||
"ramsey/uuid": "~4.9.0",
|
||||
"symfony/filesystem": "~7.3.0"
|
||||
"ramsey/uuid": "~4.7.0",
|
||||
"symfony/filesystem": "~6.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "2.1.17",
|
||||
"phpstan/phpstan-phpunit": "^2.0.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.0",
|
||||
"phpunit/phpunit": "^12.2.1"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-mbstring": "*"
|
||||
"phpstan/phpstan": "1.11.11",
|
||||
"phpstan/phpstan-phpunit": "^1.1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.2.0",
|
||||
"phpunit/phpunit": "^10.5.24"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@ -77,11 +73,12 @@
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "8.3.0"
|
||||
"php": "8.1.0"
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"scripts": {
|
||||
"make-devtools": "@php -dphar.readonly=0 tests/plugins/DevTools/src/ConsoleScript.php --make ./ --relative tests/plugins/DevTools --out plugins/DevTools.phar",
|
||||
"make-server": [
|
||||
"@composer install --no-dev --classmap-authoritative --ignore-platform-reqs",
|
||||
"@php -dphar.readonly=0 build/server-phar.php"
|
||||
|
1371
composer.lock
generated
1371
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -2,25 +2,23 @@ includes:
|
||||
- tests/phpstan/analyse-for-current-php-version.neon.php
|
||||
- tests/phpstan/configs/actual-problems.neon
|
||||
- tests/phpstan/configs/impossible-generics.neon
|
||||
- tests/phpstan/configs/php-bugs.neon
|
||||
- tests/phpstan/configs/phpstan-bugs.neon
|
||||
- tests/phpstan/configs/property-hook-sadness.neon
|
||||
- tests/phpstan/configs/reflection-class-sadness.neon
|
||||
- tests/phpstan/configs/spl-fixed-array-sucks.neon
|
||||
- vendor/phpstan/phpstan-phpunit/extension.neon
|
||||
- vendor/phpstan/phpstan-phpunit/rules.neon
|
||||
- vendor/phpstan/phpstan-strict-rules/rules.neon
|
||||
|
||||
rules:
|
||||
- pocketmine\phpstan\rules\DisallowDynamicNewRule
|
||||
- pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule
|
||||
- pocketmine\phpstan\rules\DisallowEnumComparisonRule
|
||||
- pocketmine\phpstan\rules\DisallowForeachByReferenceRule
|
||||
- pocketmine\phpstan\rules\ExplodeLimitRule
|
||||
- pocketmine\phpstan\rules\UnsafeForeachRule
|
||||
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule
|
||||
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule
|
||||
|
||||
parameters:
|
||||
level: 9
|
||||
checkMissingCallableSignature: true
|
||||
rememberPossiblyImpureFunctionValues: false #risky to remember these, better for performance to avoid repeated calls anyway
|
||||
treatPhpDocTypesAsCertain: false
|
||||
bootstrapFiles:
|
||||
- tests/phpstan/bootstrap.php
|
||||
@ -33,7 +31,6 @@ parameters:
|
||||
paths:
|
||||
- build
|
||||
- src
|
||||
- tests/phpstan/DummyPluginOwned.php
|
||||
- tests/phpstan/rules
|
||||
- tests/phpunit
|
||||
- tests/plugins/TesterPlugin
|
||||
@ -47,7 +44,6 @@ 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,6 +54,12 @@ 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.
|
||||
@ -63,6 +69,16 @@ 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.
|
||||
|
@ -1,114 +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;
|
||||
|
||||
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;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* 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 int $runs = 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->runs++;
|
||||
$this->logger->info(sprintf(
|
||||
"Run #%d took %s ms (%s -> %s roots, %s cycles collected) - cumulative GC time: %s ms",
|
||||
$this->runs,
|
||||
number_format($time / 1_000_000, 2),
|
||||
$rootsBefore,
|
||||
$rootsAfter,
|
||||
$cycles,
|
||||
number_format($this->collectionTimeTotalNs / 1_000_000, 2)
|
||||
));
|
||||
|
||||
return $cycles;
|
||||
}
|
||||
}
|
@ -1,305 +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;
|
||||
|
||||
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,24 +29,52 @@ 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;
|
||||
@ -60,8 +88,14 @@ 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;
|
||||
|
||||
@ -71,7 +105,6 @@ 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());
|
||||
}
|
||||
@ -109,10 +142,17 @@ 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{
|
||||
@ -123,6 +163,10 @@ class MemoryManager{
|
||||
return $this->globalMemoryLimit;
|
||||
}
|
||||
|
||||
public function canUseChunkCache() : bool{
|
||||
return !$this->lowMemory || !$this->lowMemDisableChunkCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allowed chunk radius based on the current memory usage.
|
||||
*/
|
||||
@ -136,19 +180,26 @@ 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)));
|
||||
foreach($this->server->getWorldManager()->getWorlds() as $world){
|
||||
$world->clearCache(true);
|
||||
if($this->lowMemClearWorldCache){
|
||||
foreach($this->server->getWorldManager()->getWorlds() as $world){
|
||||
$world->clearCache(true);
|
||||
}
|
||||
ChunkCache::pruneCaches();
|
||||
}
|
||||
ChunkCache::pruneCaches();
|
||||
|
||||
foreach($this->server->getWorldManager()->getWorlds() as $world){
|
||||
$world->doChunkGarbageCollection();
|
||||
if($this->lowMemChunkGC){
|
||||
foreach($this->server->getWorldManager()->getWorlds() as $world){
|
||||
$world->doChunkGarbageCollection();
|
||||
}
|
||||
}
|
||||
|
||||
$ev = new LowMemoryEvent($memory, $limit, $global, $triggerCount);
|
||||
$ev->call();
|
||||
|
||||
$cycles = $this->triggerGarbageCollector();
|
||||
$cycles = 0;
|
||||
if($this->garbageCollectionTrigger){
|
||||
$cycles = $this->triggerGarbageCollector();
|
||||
}
|
||||
|
||||
$this->logger->debug(sprintf("Freed %gMB, $cycles cycles", round(($ev->getMemoryFreed() / 1024) / 1024, 2)));
|
||||
}
|
||||
@ -188,8 +239,6 @@ class MemoryManager{
|
||||
if($this->garbageCollectionPeriod > 0 && ++$this->garbageCollectionTicker >= $this->garbageCollectionPeriod){
|
||||
$this->garbageCollectionTicker = 0;
|
||||
$this->triggerGarbageCollector();
|
||||
}else{
|
||||
$this->cycleGcManager->maybeCollectCycles();
|
||||
}
|
||||
|
||||
Timings::$memoryManager->stopTiming();
|
||||
@ -198,12 +247,14 @@ class MemoryManager{
|
||||
public function triggerGarbageCollector() : int{
|
||||
Timings::$garbageCollector->startTiming();
|
||||
|
||||
$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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
$cycles = gc_collect_cycles();
|
||||
@ -220,7 +271,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");
|
||||
MemoryDump::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $logger);
|
||||
self::dumpMemory($this->server, $outputFolder, $maxNesting, $maxStringSize, $logger);
|
||||
|
||||
if($this->dumpWorkers){
|
||||
$pool = $this->server->getAsyncPool();
|
||||
@ -229,4 +280,242 @@ class MemoryManager{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ namespace pocketmine {
|
||||
|
||||
require_once __DIR__ . '/VersionInfo.php';
|
||||
|
||||
const MIN_PHP_VERSION = "8.3.0";
|
||||
const MIN_PHP_VERSION = "8.1.0";
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
@ -264,7 +264,7 @@ JIT_WARNING
|
||||
$composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
|
||||
if($composerGitHash !== null){
|
||||
//we can't verify dependency versions if we were installed without using git
|
||||
$currentGitHash = explode("-", VersionInfo::GIT_HASH(), 2)[0];
|
||||
$currentGitHash = explode("-", VersionInfo::GIT_HASH())[0];
|
||||
if($currentGitHash !== $composerGitHash){
|
||||
critical_error("Composer dependencies and/or autoloader are out of sync.");
|
||||
critical_error("- Current revision is $currentGitHash");
|
||||
@ -282,11 +282,6 @@ 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,7 +36,6 @@ 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;
|
||||
@ -80,7 +79,6 @@ use pocketmine\player\PlayerDataLoadException;
|
||||
use pocketmine\player\PlayerDataProvider;
|
||||
use pocketmine\player\PlayerDataSaveException;
|
||||
use pocketmine\player\PlayerInfo;
|
||||
use pocketmine\plugin\FolderPluginLoader;
|
||||
use pocketmine\plugin\PharPluginLoader;
|
||||
use pocketmine\plugin\PluginEnableOrder;
|
||||
use pocketmine\plugin\PluginGraylist;
|
||||
@ -140,7 +138,6 @@ 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;
|
||||
@ -347,10 +344,6 @@ class Server{
|
||||
return $this->maxPlayers;
|
||||
}
|
||||
|
||||
public function setMaxPlayers(int $maxPlayers) : void{
|
||||
$this->maxPlayers = $maxPlayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the server requires that players be authenticated to Xbox Live. If true, connecting players who
|
||||
* are not logged into Xbox Live will be disconnected.
|
||||
@ -704,7 +697,7 @@ class Server{
|
||||
|
||||
public function removeOp(string $name) : void{
|
||||
$lowercaseName = strtolower($name);
|
||||
foreach(Utils::promoteKeys($this->operators->getAll()) as $operatorName => $_){
|
||||
foreach($this->operators->getAll() as $operatorName => $_){
|
||||
$operatorName = (string) $operatorName;
|
||||
if($lowercaseName === strtolower($operatorName)){
|
||||
$this->operators->remove($operatorName);
|
||||
@ -925,7 +918,6 @@ 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);
|
||||
|
||||
@ -1011,7 +1003,7 @@ class Server{
|
||||
|
||||
$this->commandMap = new SimpleCommandMap($this);
|
||||
|
||||
$this->craftingManager = CraftingManagerFromDataHelper::make(BedrockDataFiles::RECIPES);
|
||||
$this->craftingManager = CraftingManagerFromDataHelper::make(Path::join(\pocketmine\BEDROCK_DATA_PATH, "recipes"));
|
||||
|
||||
$this->resourceManager = new ResourcePackManager(Path::join($this->dataPath, "resource_packs"), $this->logger);
|
||||
|
||||
@ -1021,11 +1013,7 @@ class Server{
|
||||
copy(Path::join(\pocketmine\RESOURCE_PATH, 'plugin_list.yml'), $graylistFile);
|
||||
}
|
||||
try{
|
||||
$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);
|
||||
$pluginGraylist = PluginGraylist::fromArray(yaml_parse(Filesystem::fileGetContents($graylistFile)));
|
||||
}catch(\InvalidArgumentException $e){
|
||||
$this->logger->emergency("Failed to load $graylistFile: " . $e->getMessage());
|
||||
$this->forceShutdownExit();
|
||||
@ -1034,7 +1022,6 @@ class Server{
|
||||
$this->pluginManager = new PluginManager($this, $this->configGroup->getPropertyBool(Yml::PLUGINS_LEGACY_DATA_DIR, true) ? null : Path::join($this->dataPath, "plugin_data"), $pluginGraylist);
|
||||
$this->pluginManager->registerInterface(new PharPluginLoader($this->autoloader));
|
||||
$this->pluginManager->registerInterface(new ScriptPluginLoader());
|
||||
$this->pluginManager->registerInterface(new FolderPluginLoader($this->autoloader));
|
||||
|
||||
$providerManager = new WorldProviderManager();
|
||||
if(
|
||||
@ -1187,7 +1174,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");
|
||||
@ -1624,7 +1611,7 @@ class Server{
|
||||
if(!is_dir($crashFolder)){
|
||||
mkdir($crashFolder);
|
||||
}
|
||||
$crashDumpPath = Path::join($crashFolder, date("Y-m-d_H.i.s_T", (int) $dump->getData()->time) . ".log");
|
||||
$crashDumpPath = Path::join($crashFolder, date("D_M_j-H.i.s-T_Y", (int) $dump->getData()->time) . ".log");
|
||||
|
||||
$fp = @fopen($crashDumpPath, "wb");
|
||||
if(!is_resource($fp)){
|
||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "5.33.2";
|
||||
public const BASE_VERSION = "5.21.3";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
|
@ -75,14 +75,20 @@ 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';
|
||||
|
@ -23,10 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\PoweredByRedstone;
|
||||
use pocketmine\block\utils\RailPoweredByRedstoneTrait;
|
||||
|
||||
class ActivatorRail extends StraightOnlyRail implements PoweredByRedstone{
|
||||
class ActivatorRail extends StraightOnlyRail{
|
||||
use RailPoweredByRedstoneTrait;
|
||||
|
||||
//TODO
|
||||
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\AmethystTrait;
|
||||
use pocketmine\block\utils\AnyFacing;
|
||||
use pocketmine\block\utils\AnyFacingTrait;
|
||||
use pocketmine\block\utils\FortuneDropHelper;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
@ -39,7 +38,7 @@ use pocketmine\player\Player;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
|
||||
final class AmethystCluster extends Transparent implements AnyFacing{
|
||||
final class AmethystCluster extends Transparent{
|
||||
use AmethystTrait;
|
||||
use AnyFacingTrait;
|
||||
|
||||
@ -82,22 +81,22 @@ final class AmethystCluster extends Transparent implements AnyFacing{
|
||||
if($axis === $myAxis){
|
||||
continue;
|
||||
}
|
||||
$box = $box->squashedCopy($axis, $this->stage === self::STAGE_SMALL_BUD ? 4 / 16 : 3 / 16);
|
||||
$box->squash($axis, $this->stage === self::STAGE_SMALL_BUD ? 4 / 16 : 3 / 16);
|
||||
}
|
||||
$box = $box->trimmedCopy($this->facing, 1 - ($this->stage === self::STAGE_CLUSTER ? 7 / 16 : ($this->stage + 3) / 16));
|
||||
$box->trim($this->facing, 1 - ($this->stage === self::STAGE_CLUSTER ? 7 / 16 : ($this->stage + 3) / 16));
|
||||
|
||||
return [$box];
|
||||
}
|
||||
|
||||
private function canBeSupportedAt(Block $block, Facing $facing) : bool{
|
||||
private function canBeSupportedAt(Block $block, int $facing) : bool{
|
||||
return $block->getAdjacentSupportType($facing) === SupportType::FULL;
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){
|
||||
return false;
|
||||
}
|
||||
|
@ -23,14 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\inventory\window\AnvilInventoryWindow;
|
||||
use pocketmine\block\inventory\AnvilInventory;
|
||||
use pocketmine\block\utils\Fallable;
|
||||
use pocketmine\block\utils\FallableTrait;
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
use pocketmine\block\utils\HorizontalFacingOption;
|
||||
use pocketmine\block\utils\HorizontalFacingTrait;
|
||||
use pocketmine\block\utils\MenuAccessor;
|
||||
use pocketmine\block\utils\MenuAccessorTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\entity\object\FallingBlock;
|
||||
@ -41,15 +37,13 @@ use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\Position;
|
||||
use pocketmine\world\sound\AnvilFallSound;
|
||||
use pocketmine\world\sound\Sound;
|
||||
use function round;
|
||||
|
||||
class Anvil extends Transparent implements Fallable, HorizontalFacing, MenuAccessor{
|
||||
class Anvil extends Transparent implements Fallable{
|
||||
use FallableTrait;
|
||||
use HorizontalFacingTrait;
|
||||
use MenuAccessorTrait;
|
||||
|
||||
public const UNDAMAGED = 0;
|
||||
public const SLIGHTLY_DAMAGED = 1;
|
||||
@ -62,7 +56,7 @@ class Anvil extends Transparent implements Fallable, HorizontalFacing, MenuAcces
|
||||
}
|
||||
|
||||
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||
$w->enum($this->facing);
|
||||
$w->horizontalFacing($this->facing);
|
||||
}
|
||||
|
||||
public function getDamage() : int{ return $this->damage; }
|
||||
@ -76,21 +70,28 @@ class Anvil extends Transparent implements Fallable, HorizontalFacing, MenuAcces
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->squashedCopy(Facing::axis(Facing::rotateY($this->facing->toFacing(), false)), 1 / 8)];
|
||||
return [AxisAlignedBB::one()->squash(Facing::axis(Facing::rotateY($this->facing, false)), 1 / 8)];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
protected function newMenu(Player $player, Position $position) : AnvilInventoryWindow{
|
||||
return new AnvilInventoryWindow($player, $position);
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($player instanceof Player){
|
||||
$player->setCurrentWindow(new AnvilInventory($this->position));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($player !== null){
|
||||
$this->facing = HorizontalFacingOption::fromFacing(Facing::rotateY($player->getHorizontalFacing(), false));
|
||||
$this->facing = Facing::rotateY($player->getHorizontalFacing(), false);
|
||||
}
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
@ -87,13 +87,16 @@ 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);
|
||||
return [AxisAlignedBB::one()->trimmedCopy(Facing::SOUTH, $inset)->trimmedCopy(Facing::EAST, $inset)];
|
||||
return [AxisAlignedBB::one()->trim(Facing::SOUTH, $inset)->trim(Facing::EAST, $inset)];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
@ -138,7 +141,7 @@ class Bamboo extends Transparent{
|
||||
return $top;
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($item instanceof Fertilizer){
|
||||
$top = $this->seekToTop();
|
||||
if($top->grow(self::getMaxHeight($top->position->getFloorX(), $top->position->getFloorZ()), mt_rand(1, 2), $player)){
|
||||
|
@ -61,7 +61,7 @@ final class BambooSapling extends Flowable{
|
||||
$supportBlock->hasTypeTag(BlockTypeTags::SAND);
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($item instanceof Fertilizer || $item instanceof ItemBamboo){
|
||||
if($this->grow($player)){
|
||||
$item->pop();
|
||||
|
@ -23,33 +23,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\AnimatedContainerLike;
|
||||
use pocketmine\block\utils\AnimatedContainerLikeTrait;
|
||||
use pocketmine\block\utils\AnyFacing;
|
||||
use pocketmine\block\tile\Barrel as TileBarrel;
|
||||
use pocketmine\block\utils\AnyFacingTrait;
|
||||
use pocketmine\block\utils\Container;
|
||||
use pocketmine\block\utils\ContainerTrait;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\Position;
|
||||
use pocketmine\world\sound\BarrelCloseSound;
|
||||
use pocketmine\world\sound\BarrelOpenSound;
|
||||
use pocketmine\world\sound\Sound;
|
||||
use function abs;
|
||||
|
||||
class Barrel extends Opaque implements AnimatedContainerLike, AnyFacing, Container{
|
||||
use AnimatedContainerLikeTrait;
|
||||
class Barrel extends Opaque{
|
||||
use AnyFacingTrait;
|
||||
use ContainerTrait;
|
||||
|
||||
protected bool $open = false;
|
||||
|
||||
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||
$w->enum($this->facing);
|
||||
$w->facing($this->facing);
|
||||
$w->bool($this->open);
|
||||
}
|
||||
|
||||
@ -63,7 +53,7 @@ class Barrel extends Opaque implements AnimatedContainerLike, AnyFacing, Contain
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($player !== null){
|
||||
if(abs($player->getPosition()->x - $this->position->x) < 2 && abs($player->getPosition()->z - $this->position->z) < 2){
|
||||
$y = $player->getEyePos()->y;
|
||||
@ -83,23 +73,22 @@ class Barrel extends Opaque implements AnimatedContainerLike, AnyFacing, Contain
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($player instanceof Player){
|
||||
$barrel = $this->position->getWorld()->getTile($this->position);
|
||||
if($barrel instanceof TileBarrel){
|
||||
if(!$barrel->canOpenWith($item->getCustomName())){
|
||||
return true;
|
||||
}
|
||||
|
||||
$player->setCurrentWindow($barrel->getInventory());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFuelTime() : int{
|
||||
return 300;
|
||||
}
|
||||
|
||||
protected function getOpenSound() : Sound{
|
||||
return new BarrelOpenSound();
|
||||
}
|
||||
|
||||
protected function getCloseSound() : Sound{
|
||||
return new BarrelCloseSound();
|
||||
}
|
||||
|
||||
protected function playAnimationVisual(Position $position, bool $isOpen) : void{
|
||||
$world = $position->getWorld();
|
||||
$block = $world->getBlock($position);
|
||||
if($block instanceof Barrel){
|
||||
$world->setBlock($position, $block->setOpen($isOpen));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,20 +25,19 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\tile\Banner as TileBanner;
|
||||
use pocketmine\block\utils\BannerPatternLayer;
|
||||
use pocketmine\block\utils\Colored;
|
||||
use pocketmine\block\utils\ColoredTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\item\Banner as ItemBanner;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use function assert;
|
||||
use function count;
|
||||
|
||||
abstract class BaseBanner extends Transparent implements Colored{
|
||||
abstract class BaseBanner extends Transparent{
|
||||
use ColoredTrait;
|
||||
|
||||
/**
|
||||
@ -51,10 +50,6 @@ abstract class BaseBanner extends Transparent implements Colored{
|
||||
parent::readStateFromWorld();
|
||||
$tile = $this->position->getWorld()->getTile($this->position);
|
||||
if($tile instanceof TileBanner){
|
||||
if($tile->getType() === TileBanner::TYPE_OMINOUS){
|
||||
//illager banner is implemented as a separate block, as it doesn't support base color or custom patterns
|
||||
return $this->getOminousVersion();
|
||||
}
|
||||
$this->color = $tile->getBaseColor();
|
||||
$this->setPatterns($tile->getPatterns());
|
||||
}
|
||||
@ -62,13 +57,6 @@ abstract class BaseBanner extends Transparent implements Colored{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: make this abstract in PM6 (BC break)
|
||||
*/
|
||||
protected function getOminousVersion() : Block{
|
||||
return VanillaBlocks::AIR();
|
||||
}
|
||||
|
||||
public function writeStateToWorld() : void{
|
||||
parent::writeStateToWorld();
|
||||
$tile = $this->position->getWorld()->getTile($this->position);
|
||||
@ -109,11 +97,14 @@ abstract class BaseBanner extends Transparent implements Colored{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
@ -121,7 +112,7 @@ abstract class BaseBanner extends Transparent implements Colored{
|
||||
return $block->isSolid();
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$this->canBeSupportedBy($blockReplace->getSide($this->getSupportingFace()))){
|
||||
return false;
|
||||
}
|
||||
@ -133,7 +124,7 @@ abstract class BaseBanner extends Transparent implements Colored{
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
abstract protected function getSupportingFace() : Facing;
|
||||
abstract protected function getSupportingFace() : int;
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(!$this->canBeSupportedBy($this->getSide($this->getSupportingFace()))){
|
||||
|
@ -23,8 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
use pocketmine\block\utils\HorizontalFacingOption;
|
||||
use pocketmine\block\utils\HorizontalFacingTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\event\block\StructureGrowEvent;
|
||||
@ -35,7 +33,7 @@ use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
|
||||
abstract class BaseBigDripleaf extends Transparent implements HorizontalFacing{
|
||||
abstract class BaseBigDripleaf extends Transparent{
|
||||
use HorizontalFacingTrait;
|
||||
|
||||
abstract protected function isHead() : bool;
|
||||
@ -58,13 +56,13 @@ abstract class BaseBigDripleaf extends Transparent implements HorizontalFacing{
|
||||
}
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$block = $blockReplace->getSide(Facing::DOWN);
|
||||
if(!$this->canBeSupportedBy($block, true)){
|
||||
return false;
|
||||
}
|
||||
if($player !== null){
|
||||
$this->facing = HorizontalFacingOption::fromFacing(Facing::opposite($player->getHorizontalFacing()));
|
||||
$this->facing = Facing::opposite($player->getHorizontalFacing());
|
||||
}
|
||||
if($block instanceof BaseBigDripleaf){
|
||||
$this->facing = $block->facing;
|
||||
@ -73,7 +71,7 @@ abstract class BaseBigDripleaf extends Transparent implements HorizontalFacing{
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($item instanceof Fertilizer && $this->grow($player)){
|
||||
$item->pop();
|
||||
return true;
|
||||
@ -132,7 +130,7 @@ abstract class BaseBigDripleaf extends Transparent implements HorizontalFacing{
|
||||
return 100;
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ use pocketmine\player\Player;
|
||||
abstract class BaseCake extends Transparent implements FoodSource{
|
||||
use StaticSupportTrait;
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ abstract class BaseCake extends Transparent implements FoodSource{
|
||||
return $block->getSide(Facing::DOWN)->getTypeId() !== BlockTypeIds::AIR;
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($player !== null){
|
||||
return $player->consumeObject($this);
|
||||
}
|
||||
|
@ -24,14 +24,12 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\BlockEventHelper;
|
||||
use pocketmine\block\utils\CoralMaterial;
|
||||
use pocketmine\block\utils\CoralTypeTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Facing;
|
||||
use function mt_rand;
|
||||
|
||||
abstract class BaseCoral extends Transparent implements CoralMaterial{
|
||||
abstract class BaseCoral extends Transparent{
|
||||
use CoralTypeTrait;
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
@ -73,7 +71,7 @@ abstract class BaseCoral extends Transparent implements CoralMaterial{
|
||||
|
||||
protected function recalculateCollisionBoxes() : array{ return []; }
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
}
|
||||
|
@ -1,91 +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\block;
|
||||
|
||||
use pocketmine\block\tile\Banner as TileBanner;
|
||||
use pocketmine\block\utils\DyeColor;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use function assert;
|
||||
|
||||
abstract class BaseOminousBanner extends Transparent{
|
||||
|
||||
public function writeStateToWorld() : void{
|
||||
parent::writeStateToWorld();
|
||||
$tile = $this->position->getWorld()->getTile($this->position);
|
||||
assert($tile instanceof TileBanner);
|
||||
$tile->setBaseColor(DyeColor::WHITE);
|
||||
$tile->setPatterns([]);
|
||||
$tile->setType(TileBanner::TYPE_OMINOUS);
|
||||
}
|
||||
|
||||
public function isSolid() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMaxStackSize() : int{
|
||||
return 16;
|
||||
}
|
||||
|
||||
public function getFuelTime() : int{
|
||||
return 300;
|
||||
}
|
||||
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
private function canBeSupportedBy(Block $block) : bool{
|
||||
return $block->isSolid();
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$this->canBeSupportedBy($blockReplace->getSide($this->getSupportingFace()))){
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
abstract protected function getSupportingFace() : Facing;
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(!$this->canBeSupportedBy($this->getSide($this->getSupportingFace()))){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
}
|
||||
}
|
||||
|
||||
public function asItem() : Item{
|
||||
return VanillaItems::OMINOUS_BANNER();
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ use function in_array;
|
||||
|
||||
abstract class BaseRail extends Flowable{
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($blockReplace->getAdjacentSupportType(Facing::DOWN)->hasEdgeSupport()){
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
@ -89,9 +89,8 @@ abstract class BaseRail extends Flowable{
|
||||
|
||||
/** @var int $connection */
|
||||
foreach($this->getCurrentShapeConnections() as $connection){
|
||||
$connectionFace = Facing::from($connection & ~RailConnectionInfo::FLAG_ASCEND);
|
||||
$other = $this->getSide($connectionFace);
|
||||
$otherConnection = Facing::opposite($connectionFace)->value;
|
||||
$other = $this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND);
|
||||
$otherConnection = Facing::opposite($connection & ~RailConnectionInfo::FLAG_ASCEND);
|
||||
|
||||
if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0){
|
||||
$other = $other->getSide(Facing::UP);
|
||||
@ -123,10 +122,10 @@ abstract class BaseRail extends Flowable{
|
||||
case 0:
|
||||
//No constraints, can connect in any direction
|
||||
$possible = [
|
||||
Facing::NORTH->value => true,
|
||||
Facing::SOUTH->value => true,
|
||||
Facing::WEST->value => true,
|
||||
Facing::EAST->value => true
|
||||
Facing::NORTH => true,
|
||||
Facing::SOUTH => true,
|
||||
Facing::WEST => true,
|
||||
Facing::EAST => true
|
||||
];
|
||||
foreach($possible as $p => $_){
|
||||
$possible[$p | RailConnectionInfo::FLAG_ASCEND] = true;
|
||||
@ -147,13 +146,13 @@ abstract class BaseRail extends Flowable{
|
||||
* @phpstan-return array<int, true>
|
||||
*/
|
||||
protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{
|
||||
$opposite = Facing::opposite(Facing::from($constraint & ~RailConnectionInfo::FLAG_ASCEND));
|
||||
$opposite = Facing::opposite($constraint & ~RailConnectionInfo::FLAG_ASCEND);
|
||||
|
||||
$possible = [$opposite->value => true];
|
||||
$possible = [$opposite => true];
|
||||
|
||||
if(($constraint & RailConnectionInfo::FLAG_ASCEND) === 0){
|
||||
//We can slope the other way if this connection isn't already a slope
|
||||
$possible[$opposite->value | RailConnectionInfo::FLAG_ASCEND] = true;
|
||||
$possible[$opposite | RailConnectionInfo::FLAG_ASCEND] = true;
|
||||
}
|
||||
|
||||
return $possible;
|
||||
@ -169,10 +168,9 @@ abstract class BaseRail extends Flowable{
|
||||
$continue = false;
|
||||
|
||||
foreach($possible as $thisSide => $_){
|
||||
$thisSideEnum = Facing::from($thisSide & ~RailConnectionInfo::FLAG_ASCEND);
|
||||
$otherSide = Facing::opposite($thisSideEnum)->value;
|
||||
$otherSide = Facing::opposite($thisSide & ~RailConnectionInfo::FLAG_ASCEND);
|
||||
|
||||
$other = $this->getSide($thisSideEnum);
|
||||
$other = $this->getSide($thisSide & ~RailConnectionInfo::FLAG_ASCEND);
|
||||
|
||||
if(($thisSide & RailConnectionInfo::FLAG_ASCEND) !== 0){
|
||||
$other = $other->getSide(Facing::UP);
|
||||
@ -214,7 +212,7 @@ abstract class BaseRail extends Flowable{
|
||||
*/
|
||||
private function setConnections(array $connections) : void{
|
||||
if(count($connections) === 1){
|
||||
$connections[] = Facing::opposite(Facing::from($connections[0] & ~RailConnectionInfo::FLAG_ASCEND))->value;
|
||||
$connections[] = Facing::opposite($connections[0] & ~RailConnectionInfo::FLAG_ASCEND);
|
||||
}elseif(count($connections) !== 2){
|
||||
throw new \InvalidArgumentException("Expected exactly 2 connections, got " . count($connections));
|
||||
}
|
||||
@ -228,7 +226,7 @@ abstract class BaseRail extends Flowable{
|
||||
$world->useBreakOn($this->position);
|
||||
}else{
|
||||
foreach($this->getCurrentShapeConnections() as $connection){
|
||||
if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0 && !$this->getSide(Facing::from($connection & ~RailConnectionInfo::FLAG_ASCEND))->getSupportType(Facing::UP)->hasEdgeSupport()){
|
||||
if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0 && !$this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND)->getSupportType(Facing::UP)->hasEdgeSupport()){
|
||||
$world->useBreakOn($this->position);
|
||||
break;
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ use pocketmine\block\tile\Sign as TileSign;
|
||||
use pocketmine\block\utils\DyeColor;
|
||||
use pocketmine\block\utils\SignText;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\block\utils\WoodMaterial;
|
||||
use pocketmine\block\utils\WoodType;
|
||||
use pocketmine\block\utils\WoodTypeTrait;
|
||||
use pocketmine\color\Color;
|
||||
@ -35,26 +34,21 @@ use pocketmine\event\block\SignChangeEvent;
|
||||
use pocketmine\item\Dye;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\ItemTypeIds;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\TextFormat;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\sound\DyeUseSound;
|
||||
use pocketmine\world\sound\InkSacUseSound;
|
||||
use function abs;
|
||||
use function array_map;
|
||||
use function assert;
|
||||
use function atan2;
|
||||
use function fmod;
|
||||
use function rad2deg;
|
||||
use function strlen;
|
||||
|
||||
abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
abstract class BaseSign extends Transparent{
|
||||
use WoodTypeTrait;
|
||||
|
||||
protected SignText $text; //TODO: rename this (BC break)
|
||||
protected SignText $backText;
|
||||
protected SignText $text;
|
||||
private bool $waxed = false;
|
||||
|
||||
protected ?int $editorEntityRuntimeId = null;
|
||||
@ -69,7 +63,6 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
$this->woodType = $woodType;
|
||||
parent::__construct($idInfo, $name, $typeInfo);
|
||||
$this->text = new SignText();
|
||||
$this->backText = new SignText();
|
||||
$this->asItemCallback = $asItemCallback;
|
||||
}
|
||||
|
||||
@ -78,7 +71,6 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
$tile = $this->position->getWorld()->getTile($this->position);
|
||||
if($tile instanceof TileSign){
|
||||
$this->text = $tile->getText();
|
||||
$this->backText = $tile->getBackText();
|
||||
$this->waxed = $tile->isWaxed();
|
||||
$this->editorEntityRuntimeId = $tile->getEditorEntityRuntimeId();
|
||||
}
|
||||
@ -91,7 +83,6 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
$tile = $this->position->getWorld()->getTile($this->position);
|
||||
assert($tile instanceof TileSign);
|
||||
$tile->setText($this->text);
|
||||
$tile->setBackText($this->backText);
|
||||
$tile->setWaxed($this->waxed);
|
||||
$tile->setEditorEntityRuntimeId($this->editorEntityRuntimeId);
|
||||
}
|
||||
@ -104,15 +95,18 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
return 16;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
abstract protected function getSupportingFace() : Facing;
|
||||
abstract protected function getSupportingFace() : int;
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if($this->getSide($this->getSupportingFace())->getTypeId() === BlockTypeIds::AIR){
|
||||
@ -120,7 +114,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
}
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($player !== null){
|
||||
$this->editorEntityRuntimeId = $player->getId();
|
||||
}
|
||||
@ -136,11 +130,11 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
}
|
||||
}
|
||||
|
||||
private function doSignChange(SignText $newText, Player $player, Item $item, bool $frontFace) : bool{
|
||||
$ev = new SignChangeEvent($this, $player, $newText, $frontFace);
|
||||
private function doSignChange(SignText $newText, Player $player, Item $item) : bool{
|
||||
$ev = new SignChangeEvent($this, $player, $newText);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->setFaceText($frontFace, $ev->getNewText());
|
||||
$this->text = $ev->getNewText();
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
$item->pop();
|
||||
return true;
|
||||
@ -149,9 +143,8 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
return false;
|
||||
}
|
||||
|
||||
private function changeSignGlowingState(bool $glowing, Player $player, Item $item, bool $frontFace) : bool{
|
||||
$text = $this->getFaceText($frontFace);
|
||||
if($text->isGlowing() !== $glowing && $this->doSignChange(new SignText($text->getLines(), $text->getBaseColor(), $glowing), $player, $item, $frontFace)){
|
||||
private function changeSignGlowingState(bool $glowing, Player $player, Item $item) : bool{
|
||||
if($this->text->isGlowing() !== $glowing && $this->doSignChange(new SignText($this->text->getLines(), $this->text->getBaseColor(), $glowing), $player, $item)){
|
||||
$this->position->getWorld()->addSound($this->position, new InkSacUseSound());
|
||||
return true;
|
||||
}
|
||||
@ -170,7 +163,7 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($player === null){
|
||||
return false;
|
||||
}
|
||||
@ -178,8 +171,6 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
return true;
|
||||
}
|
||||
|
||||
$frontFace = $this->interactsFront($this->getHitboxCenter(), $player->getPosition(), $this->getFacingDegrees());
|
||||
|
||||
$dyeColor = $item instanceof Dye ? $item->getColor() : match($item->getTypeId()){
|
||||
ItemTypeIds::BONE_MEAL => DyeColor::WHITE,
|
||||
ItemTypeIds::LAPIS_LAZULI => DyeColor::BLUE,
|
||||
@ -188,82 +179,40 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
};
|
||||
if($dyeColor !== null){
|
||||
$color = $dyeColor === DyeColor::BLACK ? new Color(0, 0, 0) : $dyeColor->getRgbValue();
|
||||
$text = $this->getFaceText($frontFace);
|
||||
if(
|
||||
$color->toARGB() !== $text->getBaseColor()->toARGB() &&
|
||||
$this->doSignChange(new SignText($text->getLines(), $color, $text->isGlowing()), $player, $item, $frontFace)
|
||||
$color->toARGB() !== $this->text->getBaseColor()->toARGB() &&
|
||||
$this->doSignChange(new SignText($this->text->getLines(), $color, $this->text->isGlowing()), $player, $item)
|
||||
){
|
||||
$this->position->getWorld()->addSound($this->position, new DyeUseSound());
|
||||
return true;
|
||||
}
|
||||
}elseif(match($item->getTypeId()){
|
||||
ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item, $frontFace),
|
||||
ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item, $frontFace),
|
||||
ItemTypeIds::INK_SAC => $this->changeSignGlowingState(false, $player, $item),
|
||||
ItemTypeIds::GLOW_INK_SAC => $this->changeSignGlowingState(true, $player, $item),
|
||||
ItemTypeIds::HONEYCOMB => $this->wax($player, $item),
|
||||
default => false
|
||||
}){
|
||||
return true;
|
||||
}
|
||||
|
||||
$player->openSignEditor($this->position, $frontFace);
|
||||
$player->openSignEditor($this->position);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function interactsFront(Vector3 $hitboxCenter, Vector3 $playerPosition, float $signFacingDegrees) : bool{
|
||||
$playerCenterDiffX = $playerPosition->x - $hitboxCenter->x;
|
||||
$playerCenterDiffZ = $playerPosition->z - $hitboxCenter->z;
|
||||
|
||||
$f1 = rad2deg(atan2($playerCenterDiffZ, $playerCenterDiffX)) - 90.0;
|
||||
|
||||
$rotationDiff = $signFacingDegrees - $f1;
|
||||
$rotation = fmod($rotationDiff + 180.0, 360.0) - 180.0; // Normalize to [-180, 180]
|
||||
return abs($rotation) <= 90.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the center of the sign's hitbox. Used to decide which face of the sign to open when a player interacts.
|
||||
*/
|
||||
protected function getHitboxCenter() : Vector3{
|
||||
return $this->position->add(0.5, 0.5, 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: make this abstract (BC break)
|
||||
*/
|
||||
protected function getFacingDegrees() : float{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing information about the sign text.
|
||||
* @deprecated
|
||||
* @see self::getFaceText()
|
||||
*/
|
||||
public function getText() : SignText{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see self::setFaceText()
|
||||
* @return $this
|
||||
*/
|
||||
/** @return $this */
|
||||
public function setText(SignText $text) : self{
|
||||
$this->text = $text;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFaceText(bool $frontFace) : SignText{
|
||||
return $frontFace ? $this->text : $this->backText;
|
||||
}
|
||||
|
||||
/** @return $this */
|
||||
public function setFaceText(bool $frontFace, SignText $text) : self{
|
||||
$frontFace ? $this->text = $text : $this->backText = $text;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the sign has been waxed using a honeycomb. If true, the sign cannot be edited by a player.
|
||||
*/
|
||||
@ -288,21 +237,13 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see self::updateFaceText()
|
||||
*/
|
||||
public function updateText(Player $author, SignText $text) : bool{
|
||||
return $this->updateFaceText($author, true, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the player controller (network session) to update the sign text, firing events as appropriate.
|
||||
*
|
||||
* @return bool if the sign update was successful.
|
||||
* @throws \UnexpectedValueException if the text payload is too large
|
||||
*/
|
||||
public function updateFaceText(Player $author, bool $frontFace, SignText $text) : bool{
|
||||
public function updateText(Player $author, SignText $text) : bool{
|
||||
$size = 0;
|
||||
foreach($text->getLines() as $line){
|
||||
$size += strlen($line);
|
||||
@ -310,16 +251,15 @@ abstract class BaseSign extends Transparent implements WoodMaterial{
|
||||
if($size > 1000){
|
||||
throw new \UnexpectedValueException($author->getName() . " tried to write $size bytes of text onto a sign (bigger than max 1000)");
|
||||
}
|
||||
$oldText = $this->getFaceText($frontFace);
|
||||
$ev = new SignChangeEvent($this, $author, new SignText(array_map(function(string $line) : string{
|
||||
return TextFormat::clean($line, false);
|
||||
}, $text->getLines()), $oldText->getBaseColor(), $oldText->isGlowing()), $frontFace);
|
||||
}, $text->getLines()), $this->text->getBaseColor(), $this->text->isGlowing()));
|
||||
if($this->waxed || $this->editorEntityRuntimeId !== $author->getId()){
|
||||
$ev->cancel();
|
||||
}
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$this->setFaceText($frontFace, $ev->getNewText());
|
||||
$this->setText($ev->getNewText());
|
||||
$this->setEditorEntityRuntimeId(null);
|
||||
$this->position->getWorld()->setBlock($this->position, $this);
|
||||
return true;
|
||||
|
@ -24,11 +24,8 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\tile\Bed as TileBed;
|
||||
use pocketmine\block\utils\Colored;
|
||||
use pocketmine\block\utils\ColoredTrait;
|
||||
use pocketmine\block\utils\DyeColor;
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
use pocketmine\block\utils\HorizontalFacingOption;
|
||||
use pocketmine\block\utils\HorizontalFacingTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
@ -44,7 +41,7 @@ use pocketmine\utils\TextFormat;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\World;
|
||||
|
||||
class Bed extends Transparent implements Colored, HorizontalFacing{
|
||||
class Bed extends Transparent{
|
||||
use ColoredTrait;
|
||||
use HorizontalFacingTrait;
|
||||
|
||||
@ -52,7 +49,7 @@ class Bed extends Transparent implements Colored, HorizontalFacing{
|
||||
protected bool $head = false;
|
||||
|
||||
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||
$w->enum($this->facing);
|
||||
$w->horizontalFacing($this->facing);
|
||||
$w->bool($this->occupied);
|
||||
$w->bool($this->head);
|
||||
}
|
||||
@ -79,11 +76,14 @@ class Bed extends Transparent implements Colored, HorizontalFacing{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trimmedCopy(Facing::UP, 7 / 16)];
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 16)];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
@ -107,8 +107,8 @@ class Bed extends Transparent implements Colored, HorizontalFacing{
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getOtherHalfSide() : Facing{
|
||||
return $this->head ? Facing::opposite($this->facing->toFacing()) : $this->facing->toFacing();
|
||||
private function getOtherHalfSide() : int{
|
||||
return $this->head ? Facing::opposite($this->facing) : $this->facing;
|
||||
}
|
||||
|
||||
public function getOtherHalf() : ?Bed{
|
||||
@ -120,7 +120,7 @@ class Bed extends Transparent implements Colored, HorizontalFacing{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($player !== null){
|
||||
$other = $this->getOtherHalf();
|
||||
$playerPos = $player->getPosition();
|
||||
@ -173,11 +173,9 @@ class Bed extends Transparent implements Colored, HorizontalFacing{
|
||||
return $entity->getMotion()->y * -3 / 4; // 2/3 in Java, according to the wiki
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($this->canBeSupportedAt($blockReplace)){
|
||||
if($player !== null){
|
||||
$this->facing = HorizontalFacingOption::fromFacing($player->getHorizontalFacing());
|
||||
}
|
||||
$this->facing = $player !== null ? $player->getHorizontalFacing() : Facing::NORTH;
|
||||
|
||||
$next = $this->getSide($this->getOtherHalfSide());
|
||||
if($next->canBeReplaced() && $this->canBeSupportedAt($next)){
|
||||
|
@ -25,8 +25,6 @@ namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\tile\Bell as TileBell;
|
||||
use pocketmine\block\utils\BellAttachmentType;
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
use pocketmine\block\utils\HorizontalFacingOption;
|
||||
use pocketmine\block\utils\HorizontalFacingTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
@ -40,40 +38,39 @@ use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\sound\BellRingSound;
|
||||
|
||||
final class Bell extends Transparent implements HorizontalFacing{
|
||||
final class Bell extends Transparent{
|
||||
use HorizontalFacingTrait;
|
||||
|
||||
private BellAttachmentType $attachmentType = BellAttachmentType::FLOOR;
|
||||
|
||||
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||
$w->enum($this->attachmentType);
|
||||
$w->enum($this->facing);
|
||||
$w->horizontalFacing($this->facing);
|
||||
}
|
||||
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
$realFacing = $this->facing->toFacing();
|
||||
if($this->attachmentType === BellAttachmentType::FLOOR){
|
||||
return [
|
||||
AxisAlignedBB::one()->squashedCopy(Facing::axis($realFacing), 1 / 4)->trimmedCopy(Facing::UP, 3 / 16)
|
||||
AxisAlignedBB::one()->squash(Facing::axis($this->facing), 1 / 4)->trim(Facing::UP, 3 / 16)
|
||||
];
|
||||
}
|
||||
if($this->attachmentType === BellAttachmentType::CEILING){
|
||||
return [
|
||||
AxisAlignedBB::one()->contractedCopy(1 / 4, 0, 1 / 4)->trimmedCopy(Facing::DOWN, 1 / 4)
|
||||
AxisAlignedBB::one()->contract(1 / 4, 0, 1 / 4)->trim(Facing::DOWN, 1 / 4)
|
||||
];
|
||||
}
|
||||
|
||||
$box = AxisAlignedBB::one()
|
||||
->squashedCopy(Facing::axis(Facing::rotateY($realFacing, true)), 1 / 4)
|
||||
->trimmedCopy(Facing::UP, 1 / 16)
|
||||
->trimmedCopy(Facing::DOWN, 1 / 4);
|
||||
->squash(Facing::axis(Facing::rotateY($this->facing, true)), 1 / 4)
|
||||
->trim(Facing::UP, 1 / 16)
|
||||
->trim(Facing::DOWN, 1 / 4);
|
||||
|
||||
return [
|
||||
$this->attachmentType === BellAttachmentType::ONE_WALL ? $box->trimmedCopy($realFacing, 3 / 16) : $box
|
||||
$this->attachmentType === BellAttachmentType::ONE_WALL ? $box->trim($this->facing, 3 / 16) : $box
|
||||
];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
@ -85,23 +82,23 @@ final class Bell extends Transparent implements HorizontalFacing{
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function canBeSupportedAt(Block $block, Facing $face) : bool{
|
||||
private function canBeSupportedAt(Block $block, int $face) : bool{
|
||||
return $block->getAdjacentSupportType($face) !== SupportType::NONE;
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$this->canBeSupportedAt($blockReplace, Facing::opposite($face))){
|
||||
return false;
|
||||
}
|
||||
if($face === Facing::UP){
|
||||
if($player !== null){
|
||||
$this->setFacing(HorizontalFacingOption::fromFacing(Facing::opposite($player->getHorizontalFacing())));
|
||||
$this->setFacing(Facing::opposite($player->getHorizontalFacing()));
|
||||
}
|
||||
$this->setAttachmentType(BellAttachmentType::FLOOR);
|
||||
}elseif($face === Facing::DOWN){
|
||||
$this->setAttachmentType(BellAttachmentType::CEILING);
|
||||
}else{
|
||||
$this->setFacing(HorizontalFacingOption::fromFacing($face));
|
||||
$this->setFacing($face);
|
||||
$this->setAttachmentType(
|
||||
$this->canBeSupportedAt($blockReplace, $face) ?
|
||||
BellAttachmentType::TWO_WALLS :
|
||||
@ -115,8 +112,8 @@ final class Bell extends Transparent implements HorizontalFacing{
|
||||
foreach(match($this->attachmentType){
|
||||
BellAttachmentType::CEILING => [Facing::UP],
|
||||
BellAttachmentType::FLOOR => [Facing::DOWN],
|
||||
BellAttachmentType::ONE_WALL => [Facing::opposite($this->facing->toFacing())],
|
||||
BellAttachmentType::TWO_WALLS => [$this->facing->toFacing(), Facing::opposite($this->facing->toFacing())]
|
||||
BellAttachmentType::ONE_WALL => [Facing::opposite($this->facing)],
|
||||
BellAttachmentType::TWO_WALLS => [$this->facing, Facing::opposite($this->facing)]
|
||||
} as $supportBlockDirection){
|
||||
if(!$this->canBeSupportedAt($this, $supportBlockDirection)){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
@ -125,7 +122,7 @@ final class Bell extends Transparent implements HorizontalFacing{
|
||||
}
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($player !== null){
|
||||
$faceHit = Facing::opposite($player->getHorizontalFacing());
|
||||
if($this->isValidFaceToRing($faceHit)){
|
||||
@ -144,7 +141,7 @@ final class Bell extends Transparent implements HorizontalFacing{
|
||||
}
|
||||
}
|
||||
|
||||
public function ring(Facing $faceHit) : void{
|
||||
public function ring(int $faceHit) : void{
|
||||
$world = $this->position->getWorld();
|
||||
$world->addSound($this->position, new BellRingSound());
|
||||
$tile = $world->getTile($this->position);
|
||||
@ -157,11 +154,11 @@ final class Bell extends Transparent implements HorizontalFacing{
|
||||
return [$this->asItem()];
|
||||
}
|
||||
|
||||
private function isValidFaceToRing(Facing $faceHit) : bool{
|
||||
private function isValidFaceToRing(int $faceHit) : bool{
|
||||
return match($this->attachmentType){
|
||||
BellAttachmentType::CEILING => true,
|
||||
BellAttachmentType::FLOOR => Facing::axis($faceHit) === Facing::axis($this->facing->toFacing()),
|
||||
BellAttachmentType::ONE_WALL, BellAttachmentType::TWO_WALLS => $faceHit === Facing::rotateY($this->facing->toFacing(), false) || $faceHit === Facing::rotateY($this->facing->toFacing(), true),
|
||||
BellAttachmentType::FLOOR => Facing::axis($faceHit) === Facing::axis($this->facing),
|
||||
BellAttachmentType::ONE_WALL, BellAttachmentType::TWO_WALLS => $faceHit === Facing::rotateY($this->facing, false) || $faceHit === Facing::rotateY($this->facing, true),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -80,8 +80,8 @@ class BigDripleafHead extends BaseBigDripleaf{
|
||||
if(!$entity instanceof Projectile && $this->leafState === DripleafState::STABLE){
|
||||
//the entity must be standing on top of the leaf - do not collapse if the entity is standing underneath
|
||||
$intersection = AxisAlignedBB::one()
|
||||
->offsetCopy($this->position->x, $this->position->y, $this->position->z)
|
||||
->trimmedCopy(Facing::DOWN, 1 - $this->getLeafTopOffset());
|
||||
->offset($this->position->x, $this->position->y, $this->position->z)
|
||||
->trim(Facing::DOWN, 1 - $this->getLeafTopOffset());
|
||||
if($entity->getBoundingBox()->intersectsWith($intersection)){
|
||||
$this->setTiltAndScheduleTick(DripleafState::UNSTABLE);
|
||||
return false;
|
||||
@ -116,8 +116,8 @@ class BigDripleafHead extends BaseBigDripleaf{
|
||||
if($this->leafState !== DripleafState::FULL_TILT){
|
||||
return [
|
||||
AxisAlignedBB::one()
|
||||
->trimmedCopy(Facing::DOWN, 11 / 16)
|
||||
->trimmedCopy(Facing::UP, $this->getLeafTopOffset())
|
||||
->trim(Facing::DOWN, 11 / 16)
|
||||
->trim(Facing::UP, $this->getLeafTopOffset())
|
||||
];
|
||||
}
|
||||
return [];
|
||||
|
@ -75,10 +75,7 @@ class Block{
|
||||
protected BlockTypeInfo $typeInfo;
|
||||
protected Position $position;
|
||||
|
||||
/**
|
||||
* @var AxisAlignedBB[]|null
|
||||
* @phpstan-var list<AxisAlignedBB>|null
|
||||
*/
|
||||
/** @var AxisAlignedBB[]|null */
|
||||
protected ?array $collisionBoxes = null;
|
||||
|
||||
private int $requiredBlockItemStateDataBits;
|
||||
@ -424,7 +421,7 @@ class Block{
|
||||
* Returns whether this block can replace the given block in the given placement conditions.
|
||||
* This is used to allow slabs of the same type to combine into double slabs.
|
||||
*/
|
||||
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, Facing $face, bool $isClickedBlock) : bool{
|
||||
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
|
||||
return $blockReplace->canBeReplaced();
|
||||
}
|
||||
|
||||
@ -436,13 +433,13 @@ class Block{
|
||||
* @param Item $item Item used to place the block
|
||||
* @param Block $blockReplace Block expected to be replaced
|
||||
* @param Block $blockClicked Block that was clicked using the item
|
||||
* @param Facing $face Face of the clicked block which was clicked
|
||||
* @param int $face Face of the clicked block which was clicked
|
||||
* @param Vector3 $clickVector Exact position inside the clicked block where the click occurred, relative to the block's position
|
||||
* @param Player|null $player Player who placed the block, or null if it was not a player
|
||||
*
|
||||
* @return bool whether the placement should go ahead
|
||||
*/
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$tx->addBlock($blockReplace->position, $this);
|
||||
return true;
|
||||
}
|
||||
@ -524,7 +521,7 @@ class Block{
|
||||
* @param Vector3 $clickVector Exact position where the click occurred, relative to the block's integer position
|
||||
* @param Item[] &$returnedItems Items to be added to the target's inventory (or dropped, if the inventory is full)
|
||||
*/
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -533,7 +530,7 @@ class Block{
|
||||
*
|
||||
* @return bool if an action took place, prevents starting to break the block if true.
|
||||
*/
|
||||
public function onAttack(Item $item, Facing $face, ?Player $player = null) : bool{
|
||||
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -770,10 +767,10 @@ class Block{
|
||||
*
|
||||
* @return Block
|
||||
*/
|
||||
public function getSide(Facing $side, int $step = 1){
|
||||
public function getSide(int $side, int $step = 1){
|
||||
$position = $this->position;
|
||||
if($position->isValid()){
|
||||
[$dx, $dy, $dz] = Facing::OFFSET[$side->value] ?? [0, 0, 0];
|
||||
[$dx, $dy, $dz] = Facing::OFFSET[$side] ?? [0, 0, 0];
|
||||
return $position->getWorld()->getBlockAt(
|
||||
$position->x + ($dx * $step),
|
||||
$position->y + ($dy * $step),
|
||||
@ -793,7 +790,7 @@ class Block{
|
||||
public function getHorizontalSides() : \Generator{
|
||||
$world = $this->position->getWorld();
|
||||
foreach(Facing::HORIZONTAL as $facing){
|
||||
[$dx, $dy, $dz] = Facing::OFFSET[$facing->value];
|
||||
[$dx, $dy, $dz] = Facing::OFFSET[$facing];
|
||||
//TODO: yield Facing as the key?
|
||||
yield $world->getBlockAt(
|
||||
$this->position->x + $dx,
|
||||
@ -910,16 +907,14 @@ class Block{
|
||||
* - anti-cheat checks in plugins
|
||||
*
|
||||
* @return AxisAlignedBB[]
|
||||
* @phpstan-return list<AxisAlignedBB>
|
||||
*/
|
||||
final public function getCollisionBoxes() : array{
|
||||
if($this->collisionBoxes === null){
|
||||
$collisionBoxes = $this->recalculateCollisionBoxes();
|
||||
$this->collisionBoxes = $this->recalculateCollisionBoxes();
|
||||
$extraOffset = $this->getModelPositionOffset();
|
||||
$offset = $extraOffset !== null ? $this->position->addVector($extraOffset) : $this->position;
|
||||
$this->collisionBoxes = [];
|
||||
foreach($collisionBoxes as $bb){
|
||||
$this->collisionBoxes[] = $bb->offsetCopy($offset->x, $offset->y, $offset->z);
|
||||
foreach($this->collisionBoxes as $bb){
|
||||
$bb->offset($offset->x, $offset->y, $offset->z);
|
||||
}
|
||||
}
|
||||
|
||||
@ -936,7 +931,6 @@ class Block{
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
* @phpstan-return list<AxisAlignedBB>
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()];
|
||||
@ -946,11 +940,11 @@ class Block{
|
||||
* Returns the type of support that the block can provide on the given face. This is used to determine whether
|
||||
* blocks placed on the given face can be supported by this block.
|
||||
*/
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::FULL;
|
||||
}
|
||||
|
||||
protected function getAdjacentSupportType(Facing $facing) : SupportType{
|
||||
protected function getAdjacentSupportType(int $facing) : SupportType{
|
||||
return $this->getSide($facing)->getSupportType(Facing::opposite($facing));
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ class BlockBreakInfo{
|
||||
return new self(0.0, $toolType, $toolHarvestLevel, 0.0);
|
||||
}
|
||||
|
||||
public static function indestructible(float $blastResistance = 18000003.75) : self{
|
||||
public static function indestructible(float $blastResistance = 18000000.0) : self{
|
||||
return new self(-1.0, BlockToolType::NONE, 0, $blastResistance);
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ class BlockBreakInfo{
|
||||
* Returns whether this block can be instantly broken.
|
||||
*/
|
||||
public function breaksInstantly() : bool{
|
||||
return $this->hardness === 0.0;
|
||||
return $this->hardness == 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,7 +154,7 @@ class BlockBreakInfo{
|
||||
|
||||
$efficiency = $item->getMiningEfficiency(($this->toolType & $item->getBlockToolType()) !== 0);
|
||||
if($efficiency <= 0){
|
||||
throw new \InvalidArgumentException(get_class($item) . " must have a positive mining efficiency, but got $efficiency");
|
||||
throw new \InvalidArgumentException(get_class($item) . " has invalid mining efficiency: expected >= 0, got $efficiency");
|
||||
}
|
||||
|
||||
$base /= $efficiency;
|
||||
|
@ -765,65 +765,8 @@ 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 RESPAWN_ANCHOR = 10759;
|
||||
public const OMINOUS_BANNER = 10760;
|
||||
public const OMINOUS_WALL_BANNER = 10761;
|
||||
public const ACACIA_CEILING_CENTER_HANGING_SIGN = 10762;
|
||||
public const ACACIA_CEILING_EDGES_HANGING_SIGN = 10763;
|
||||
public const ACACIA_WALL_HANGING_SIGN = 10764;
|
||||
public const BIRCH_CEILING_CENTER_HANGING_SIGN = 10765;
|
||||
public const BIRCH_CEILING_EDGES_HANGING_SIGN = 10766;
|
||||
public const BIRCH_WALL_HANGING_SIGN = 10767;
|
||||
public const CHERRY_CEILING_CENTER_HANGING_SIGN = 10768;
|
||||
public const CHERRY_CEILING_EDGES_HANGING_SIGN = 10769;
|
||||
public const CHERRY_WALL_HANGING_SIGN = 10770;
|
||||
public const CRIMSON_CEILING_CENTER_HANGING_SIGN = 10771;
|
||||
public const CRIMSON_CEILING_EDGES_HANGING_SIGN = 10772;
|
||||
public const CRIMSON_WALL_HANGING_SIGN = 10773;
|
||||
public const DARK_OAK_CEILING_CENTER_HANGING_SIGN = 10774;
|
||||
public const DARK_OAK_CEILING_EDGES_HANGING_SIGN = 10775;
|
||||
public const DARK_OAK_WALL_HANGING_SIGN = 10776;
|
||||
public const JUNGLE_CEILING_CENTER_HANGING_SIGN = 10777;
|
||||
public const JUNGLE_CEILING_EDGES_HANGING_SIGN = 10778;
|
||||
public const JUNGLE_WALL_HANGING_SIGN = 10779;
|
||||
public const MANGROVE_CEILING_CENTER_HANGING_SIGN = 10780;
|
||||
public const MANGROVE_CEILING_EDGES_HANGING_SIGN = 10781;
|
||||
public const MANGROVE_WALL_HANGING_SIGN = 10782;
|
||||
public const OAK_CEILING_CENTER_HANGING_SIGN = 10783;
|
||||
public const OAK_CEILING_EDGES_HANGING_SIGN = 10784;
|
||||
public const OAK_WALL_HANGING_SIGN = 10785;
|
||||
public const PALE_OAK_CEILING_CENTER_HANGING_SIGN = 10786;
|
||||
public const PALE_OAK_CEILING_EDGES_HANGING_SIGN = 10787;
|
||||
public const PALE_OAK_WALL_HANGING_SIGN = 10788;
|
||||
public const SPRUCE_CEILING_CENTER_HANGING_SIGN = 10789;
|
||||
public const SPRUCE_CEILING_EDGES_HANGING_SIGN = 10790;
|
||||
public const SPRUCE_WALL_HANGING_SIGN = 10791;
|
||||
public const WARPED_CEILING_CENTER_HANGING_SIGN = 10792;
|
||||
public const WARPED_CEILING_EDGES_HANGING_SIGN = 10793;
|
||||
public const WARPED_WALL_HANGING_SIGN = 10794;
|
||||
|
||||
public const FIRST_UNUSED_BLOCK_ID = 10795;
|
||||
public const FIRST_UNUSED_BLOCK_ID = 10738;
|
||||
|
||||
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;
|
||||
|
||||
|
@ -31,5 +31,4 @@ final class BlockTypeTags{
|
||||
public const SAND = self::PREFIX . "sand";
|
||||
public const POTTABLE_PLANTS = self::PREFIX . "pottable";
|
||||
public const FIRE = self::PREFIX . "fire";
|
||||
public const HANGING_SIGN = self::PREFIX . "hanging_sign";
|
||||
}
|
||||
|
@ -23,9 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\PillarRotation;
|
||||
use pocketmine\block\utils\PillarRotationTrait;
|
||||
|
||||
class BoneBlock extends Opaque implements PillarRotation{
|
||||
class BoneBlock extends Opaque{
|
||||
use PillarRotationTrait;
|
||||
}
|
||||
|
@ -23,24 +23,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\inventory\window\BrewingStandInventoryWindow;
|
||||
use pocketmine\block\tile\BrewingStand as TileBrewingStand;
|
||||
use pocketmine\block\utils\BrewingStandSlot;
|
||||
use pocketmine\block\utils\Container;
|
||||
use pocketmine\block\utils\ContainerTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Axis;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\Position;
|
||||
use function array_key_exists;
|
||||
use function spl_object_id;
|
||||
|
||||
class BrewingStand extends Transparent implements Container{
|
||||
use ContainerTrait;
|
||||
class BrewingStand extends Transparent{
|
||||
|
||||
/**
|
||||
* @var BrewingStandSlot[]
|
||||
@ -55,17 +51,17 @@ class BrewingStand extends Transparent implements Container{
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [
|
||||
//bottom slab part - in PC this is also inset on X/Z by 1/16, but Bedrock sucks
|
||||
AxisAlignedBB::one()->trimmedCopy(Facing::UP, 7 / 8),
|
||||
AxisAlignedBB::one()->trim(Facing::UP, 7 / 8),
|
||||
|
||||
//center post
|
||||
AxisAlignedBB::one()
|
||||
->squashedCopy(Axis::X, 7 / 16)
|
||||
->squashedCopy(Axis::Z, 7 / 16)
|
||||
->trimmedCopy(Facing::UP, 1 / 8)
|
||||
->squash(Axis::X, 7 / 16)
|
||||
->squash(Axis::Z, 7 / 16)
|
||||
->trim(Facing::UP, 1 / 8)
|
||||
];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
@ -99,8 +95,15 @@ class BrewingStand extends Transparent implements Container{
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function newMenu(Player $player, Inventory $inventory, Position $position) : BrewingStandInventoryWindow{
|
||||
return new BrewingStandInventoryWindow($player, $inventory, $position);
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($player instanceof Player){
|
||||
$stand = $this->position->getWorld()->getTile($this->position);
|
||||
if($stand instanceof TileBrewingStand && $stand->canOpenWith($item->getCustomName())){
|
||||
$player->setCurrentWindow($stand->getInventory());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onScheduledUpdate() : void{
|
||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\AnyFacing;
|
||||
use pocketmine\block\utils\AnyFacingTrait;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\item\Item;
|
||||
@ -34,13 +33,13 @@ use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\sound\RedstonePowerOffSound;
|
||||
use pocketmine\world\sound\RedstonePowerOnSound;
|
||||
|
||||
abstract class Button extends Flowable implements AnyFacing{
|
||||
abstract class Button extends Flowable{
|
||||
use AnyFacingTrait;
|
||||
|
||||
protected bool $pressed = false;
|
||||
|
||||
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||
$w->enum($this->facing);
|
||||
$w->facing($this->facing);
|
||||
$w->bool($this->pressed);
|
||||
}
|
||||
|
||||
@ -52,7 +51,7 @@ abstract class Button extends Flowable implements AnyFacing{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($this->canBeSupportedAt($blockReplace, $face)){
|
||||
$this->facing = $face;
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
@ -62,7 +61,7 @@ abstract class Button extends Flowable implements AnyFacing{
|
||||
|
||||
abstract protected function getActivationTime() : int;
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if(!$this->pressed){
|
||||
$this->pressed = true;
|
||||
$world = $this->position->getWorld();
|
||||
@ -89,7 +88,7 @@ abstract class Button extends Flowable implements AnyFacing{
|
||||
}
|
||||
}
|
||||
|
||||
private function canBeSupportedAt(Block $block, Facing $face) : bool{
|
||||
private function canBeSupportedAt(Block $block, int $face) : bool{
|
||||
return $block->getAdjacentSupportType(Facing::opposite($face))->hasCenterSupport();
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\Ageable;
|
||||
use pocketmine\block\utils\AgeableTrait;
|
||||
use pocketmine\block\utils\BlockEventHelper;
|
||||
use pocketmine\block\utils\StaticSupportTrait;
|
||||
@ -34,7 +33,7 @@ use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
|
||||
class Cactus extends Transparent implements Ageable{
|
||||
class Cactus extends Transparent{
|
||||
use AgeableTrait;
|
||||
use StaticSupportTrait;
|
||||
|
||||
@ -44,12 +43,15 @@ class Cactus extends Transparent implements Ageable{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
$shrinkSize = 1 / 16;
|
||||
return [AxisAlignedBB::one()->contractedCopy($shrinkSize, 0, $shrinkSize)->trimmedCopy(Facing::UP, $shrinkSize)];
|
||||
return [AxisAlignedBB::one()->contract($shrinkSize, 0, $shrinkSize)->trim(Facing::UP, $shrinkSize)];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
|
@ -40,12 +40,15 @@ class Cake extends BaseCake{
|
||||
$w->boundedIntAuto(0, self::MAX_BITES, $this->bites);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [
|
||||
AxisAlignedBB::one()
|
||||
->contractedCopy(1 / 16, 0, 1 / 16)
|
||||
->trimmedCopy(Facing::UP, 0.5)
|
||||
->trimmedCopy(Facing::WEST, $this->bites / 8)
|
||||
->contract(1 / 16, 0, 1 / 16)
|
||||
->trim(Facing::UP, 0.5)
|
||||
->trim(Facing::WEST, $this->bites / 8)
|
||||
];
|
||||
}
|
||||
|
||||
@ -60,7 +63,7 @@ class Cake extends BaseCake{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($this->bites === 0 && $item instanceof ItemBlock){
|
||||
$block = $item->getBlock();
|
||||
$resultBlock = null;
|
||||
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\CandleTrait;
|
||||
use pocketmine\block\utils\Lightable;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
@ -32,16 +31,19 @@ use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
class CakeWithCandle extends BaseCake implements Lightable{
|
||||
class CakeWithCandle extends BaseCake{
|
||||
use CandleTrait {
|
||||
onInteract as onInteractCandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [
|
||||
AxisAlignedBB::one()
|
||||
->contractedCopy(1 / 16, 0, 1 / 16)
|
||||
->trimmedCopy(Facing::UP, 0.5) //TODO: not sure if the candle affects height
|
||||
->contract(1 / 16, 0, 1 / 16)
|
||||
->trim(Facing::UP, 0.5) //TODO: not sure if the candle affects height
|
||||
];
|
||||
}
|
||||
|
||||
@ -49,7 +51,7 @@ class CakeWithCandle extends BaseCake implements Lightable{
|
||||
return VanillaBlocks::CANDLE();
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($this->lit && $face !== Facing::UP){
|
||||
return true;
|
||||
}
|
||||
|
@ -23,11 +23,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\Colored;
|
||||
use pocketmine\block\utils\ColoredTrait;
|
||||
use pocketmine\block\utils\DyeColor;
|
||||
|
||||
class CakeWithDyedCandle extends CakeWithCandle implements Colored{
|
||||
class CakeWithDyedCandle extends CakeWithCandle{
|
||||
use ColoredTrait;
|
||||
|
||||
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
|
||||
|
@ -23,11 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\inventory\CampfireInventory;
|
||||
use pocketmine\block\tile\Campfire as TileCampfire;
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
use pocketmine\block\utils\HorizontalFacingOption;
|
||||
use pocketmine\block\utils\HorizontalFacingTrait;
|
||||
use pocketmine\block\utils\Lightable;
|
||||
use pocketmine\block\utils\LightableTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\crafting\FurnaceRecipe;
|
||||
@ -40,7 +38,6 @@ use pocketmine\entity\projectile\SplashPotion;
|
||||
use pocketmine\event\block\CampfireCookEvent;
|
||||
use pocketmine\event\entity\EntityDamageByBlockEvent;
|
||||
use pocketmine\event\entity\EntityDamageEvent;
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\enchantment\VanillaEnchantments;
|
||||
use pocketmine\item\Item;
|
||||
@ -62,7 +59,7 @@ use function count;
|
||||
use function min;
|
||||
use function mt_rand;
|
||||
|
||||
class Campfire extends Transparent implements Lightable, HorizontalFacing{
|
||||
class Campfire extends Transparent{
|
||||
use HorizontalFacingTrait{
|
||||
HorizontalFacingTrait::describeBlockOnlyState as encodeFacingState;
|
||||
}
|
||||
@ -76,7 +73,7 @@ class Campfire extends Transparent implements Lightable, HorizontalFacing{
|
||||
* @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block
|
||||
* has never been set in the world.
|
||||
*/
|
||||
protected ?Inventory $inventory = null;
|
||||
protected CampfireInventory $inventory;
|
||||
|
||||
/**
|
||||
* @var int[] slot => ticks
|
||||
@ -96,8 +93,7 @@ class Campfire extends Transparent implements Lightable, HorizontalFacing{
|
||||
$this->inventory = $tile->getInventory();
|
||||
$this->cookingTimes = $tile->getCookingTimes();
|
||||
}else{
|
||||
$this->inventory = null;
|
||||
$this->cookingTimes = [];
|
||||
$this->inventory = new CampfireInventory($this->position);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -129,19 +125,19 @@ class Campfire extends Transparent implements Lightable, HorizontalFacing{
|
||||
];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trimmedCopy(Facing::UP, 9 / 16)];
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block
|
||||
* has never been set in the world.
|
||||
*/
|
||||
public function getInventory() : ?Inventory{
|
||||
public function getInventory() : CampfireInventory{
|
||||
return $this->inventory;
|
||||
}
|
||||
|
||||
@ -173,18 +169,18 @@ class Campfire extends Transparent implements Lightable, HorizontalFacing{
|
||||
return $this->cookingTimes[$slot] ?? 0;
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($this->getSide(Facing::DOWN) instanceof Campfire){
|
||||
return false;
|
||||
}
|
||||
if($player !== null){
|
||||
$this->facing = HorizontalFacingOption::fromFacing($player->getHorizontalFacing());
|
||||
$this->facing = $player->getHorizontalFacing();
|
||||
}
|
||||
$this->lit = true;
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if(!$this->lit){
|
||||
if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE){
|
||||
$item->pop();
|
||||
@ -204,11 +200,10 @@ class Campfire extends Transparent implements Lightable, HorizontalFacing{
|
||||
return true;
|
||||
}
|
||||
|
||||
$inventory = $this->inventory;
|
||||
if($inventory !== null && $this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($this->getFurnaceType())->match($item) !== null){
|
||||
if($this->position->getWorld()->getServer()->getCraftingManager()->getFurnaceRecipeManager($this->getFurnaceType())->match($item) !== null){
|
||||
$ingredient = clone $item;
|
||||
$ingredient->setCount(1);
|
||||
if(count($inventory->addItem($ingredient)) === 0){
|
||||
if(count($this->inventory->addItem($ingredient)) === 0){
|
||||
$item->pop();
|
||||
$this->position->getWorld()->addSound($this->position, new ItemFrameAddItemSound());
|
||||
return true;
|
||||
@ -243,8 +238,8 @@ class Campfire extends Transparent implements Lightable, HorizontalFacing{
|
||||
}
|
||||
|
||||
public function onScheduledUpdate() : void{
|
||||
if($this->lit && ($inventory = $this->inventory) !== null){
|
||||
$items = $inventory->getContents();
|
||||
if($this->lit){
|
||||
$items = $this->inventory->getContents();
|
||||
$furnaceType = $this->getFurnaceType();
|
||||
$maxCookDuration = $furnaceType->getCookDurationTicks();
|
||||
foreach($items as $slot => $item){
|
||||
@ -262,7 +257,7 @@ class Campfire extends Transparent implements Lightable, HorizontalFacing{
|
||||
continue;
|
||||
}
|
||||
|
||||
$inventory->setItem($slot, VanillaItems::AIR());
|
||||
$this->inventory->setItem($slot, VanillaItems::AIR());
|
||||
$this->setCookingTime($slot, 0);
|
||||
$this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $ev->getResult());
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\CandleTrait;
|
||||
use pocketmine\block\utils\Lightable;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\item\Item;
|
||||
@ -36,7 +35,7 @@ use pocketmine\player\Player;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
|
||||
class Candle extends Transparent implements Lightable{
|
||||
class Candle extends Transparent{
|
||||
use CandleTrait {
|
||||
describeBlockOnlyState as encodeLitState;
|
||||
getLightLevel as getBaseLightLevel;
|
||||
@ -71,27 +70,27 @@ class Candle extends Transparent implements Lightable{
|
||||
return [
|
||||
(match($this->count){
|
||||
1 => AxisAlignedBB::one()
|
||||
->squashedCopy(Axis::X, 7 / 16)
|
||||
->squashedCopy(Axis::Z, 7 / 16),
|
||||
->squash(Axis::X, 7 / 16)
|
||||
->squash(Axis::Z, 7 / 16),
|
||||
2 => AxisAlignedBB::one()
|
||||
->squashedCopy(Axis::X, 5 / 16)
|
||||
->trimmedCopy(Facing::NORTH, 7 / 16) //0.3 thick on the Z axis
|
||||
->trimmedCopy(Facing::SOUTH, 6 / 16),
|
||||
->squash(Axis::X, 5 / 16)
|
||||
->trim(Facing::NORTH, 7 / 16) //0.3 thick on the Z axis
|
||||
->trim(Facing::SOUTH, 6 / 16),
|
||||
3 => AxisAlignedBB::one()
|
||||
->trimmedCopy(Facing::WEST, 5 / 16)
|
||||
->trimmedCopy(Facing::EAST, 6 / 16)
|
||||
->trimmedCopy(Facing::NORTH, 6 / 16)
|
||||
->trimmedCopy(Facing::SOUTH, 5 / 16),
|
||||
->trim(Facing::WEST, 5 / 16)
|
||||
->trim(Facing::EAST, 6 / 16)
|
||||
->trim(Facing::NORTH, 6 / 16)
|
||||
->trim(Facing::SOUTH, 5 / 16),
|
||||
4 => AxisAlignedBB::one()
|
||||
->squashedCopy(Axis::X, 5 / 16)
|
||||
->trimmedCopy(Facing::NORTH, 5 / 16)
|
||||
->trimmedCopy(Facing::SOUTH, 6 / 16),
|
||||
->squash(Axis::X, 5 / 16)
|
||||
->trim(Facing::NORTH, 5 / 16)
|
||||
->trim(Facing::SOUTH, 6 / 16),
|
||||
default => throw new AssumptionFailedError("Unreachable")
|
||||
})->trimmedCopy(Facing::UP, 10 / 16)
|
||||
})->trim(Facing::UP, 10 / 16)
|
||||
];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
@ -99,12 +98,12 @@ class Candle extends Transparent implements Lightable{
|
||||
return $block instanceof Candle && $block->hasSameTypeId($this) ? $block : null;
|
||||
}
|
||||
|
||||
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, Facing $face, bool $isClickedBlock) : bool{
|
||||
public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{
|
||||
$candle = $this->getCandleIfCompatibleType($blockReplace);
|
||||
return $candle !== null ? $candle->count < self::MAX_COUNT : parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(!$blockReplace->getAdjacentSupportType(Facing::DOWN)->hasCenterSupport()){
|
||||
return false;
|
||||
}
|
||||
|
@ -23,13 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\Colored;
|
||||
use pocketmine\block\utils\ColoredTrait;
|
||||
use pocketmine\block\utils\StaticSupportTrait;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
|
||||
class Carpet extends Flowable implements Colored{
|
||||
class Carpet extends Flowable{
|
||||
use ColoredTrait;
|
||||
use StaticSupportTrait;
|
||||
|
||||
@ -37,8 +36,11 @@ class Carpet extends Flowable implements Colored{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()->trimmedCopy(Facing::UP, 15 / 16)];
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 15 / 16)];
|
||||
}
|
||||
|
||||
private function canBeSupportedAt(Block $block) : bool{
|
||||
|
@ -23,17 +23,19 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\inventory\window\CartographyTableInventoryWindow;
|
||||
use pocketmine\block\utils\MenuAccessor;
|
||||
use pocketmine\block\utils\MenuAccessorTrait;
|
||||
use pocketmine\block\inventory\CartographyTableInventory;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\Position;
|
||||
|
||||
final class CartographyTable extends Opaque implements MenuAccessor{
|
||||
use MenuAccessorTrait;
|
||||
final class CartographyTable extends Opaque{
|
||||
|
||||
protected function newMenu(Player $player, Position $position) : CartographyTableInventoryWindow{
|
||||
return new CartographyTableInventoryWindow($player, $position);
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($player !== null){
|
||||
$player->setCurrentWindow(new CartographyTableInventory($this->position));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFuelTime() : int{
|
||||
|
@ -24,8 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
|
||||
class CarvedPumpkin extends Opaque implements HorizontalFacing{
|
||||
class CarvedPumpkin extends Opaque{
|
||||
use FacesOppositePlacingPlayerTrait;
|
||||
}
|
||||
|
@ -51,16 +51,16 @@ final class Cauldron extends Transparent{
|
||||
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
$result = [
|
||||
AxisAlignedBB::one()->trimmedCopy(Facing::UP, 11 / 16) //bottom of the cauldron
|
||||
AxisAlignedBB::one()->trim(Facing::UP, 11 / 16) //bottom of the cauldron
|
||||
];
|
||||
|
||||
foreach(Facing::HORIZONTAL as $f){ //add the frame parts around the bowl
|
||||
$result[] = AxisAlignedBB::one()->trimmedCopy($f, 14 / 16);
|
||||
$result[] = AxisAlignedBB::one()->trim($f, 14 / 16);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return $facing === Facing::UP ? SupportType::EDGE : SupportType::NONE;
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ final class Cauldron extends Transparent{
|
||||
$returnedItems[] = $returnedItem;
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($item->getTypeId() === ItemTypeIds::WATER_BUCKET){
|
||||
$this->fill(FillableCauldron::MAX_FILL_LEVEL, VanillaBlocks::WATER_CAULDRON(), $item, VanillaItems::BUCKET(), $returnedItems);
|
||||
}elseif($item->getTypeId() === ItemTypeIds::LAVA_BUCKET){
|
||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\Ageable;
|
||||
use pocketmine\block\utils\AgeableTrait;
|
||||
use pocketmine\block\utils\BlockEventHelper;
|
||||
use pocketmine\block\utils\StaticSupportTrait;
|
||||
@ -40,7 +39,7 @@ use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\sound\GlowBerriesPickSound;
|
||||
use function mt_rand;
|
||||
|
||||
class CaveVines extends Flowable implements Ageable{
|
||||
class CaveVines extends Flowable{
|
||||
use AgeableTrait;
|
||||
use StaticSupportTrait;
|
||||
|
||||
@ -84,12 +83,12 @@ class CaveVines extends Flowable implements Ageable{
|
||||
return $supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL || $supportBlock->hasSameTypeId($this);
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
$this->age = mt_rand(0, self::MAX_AGE);
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($this->berries){
|
||||
$this->position->getWorld()->dropItem($this->position, $this->asItem());
|
||||
$this->position->getWorld()->addSound($this->position, new GlowBerriesPickSound());
|
||||
@ -159,7 +158,7 @@ class CaveVines extends Flowable implements Ageable{
|
||||
return VanillaItems::GLOW_BERRIES();
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
}
|
||||
|
@ -1,65 +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\block;
|
||||
|
||||
use pocketmine\block\utils\SignLikeRotation;
|
||||
use pocketmine\block\utils\SignLikeRotationTrait;
|
||||
use pocketmine\block\utils\StaticSupportTrait;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
|
||||
final class CeilingCenterHangingSign extends BaseSign implements SignLikeRotation{
|
||||
use SignLikeRotationTrait;
|
||||
use StaticSupportTrait;
|
||||
|
||||
protected function getSupportingFace() : Facing{
|
||||
return Facing::UP;
|
||||
}
|
||||
|
||||
//TODO: duplicated code :(
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($face !== Facing::DOWN){
|
||||
return false;
|
||||
}
|
||||
|
||||
if($player !== null){
|
||||
$this->rotation = self::getRotationFromYaw($player->getLocation()->getYaw());
|
||||
}
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
private function canBeSupportedAt(Block $block) : bool{
|
||||
$supportBlock = $block->getSide(Facing::UP);
|
||||
return
|
||||
$supportBlock->getSupportType(Facing::DOWN)->hasCenterSupport() ||
|
||||
$supportBlock->hasTypeTag(BlockTypeTags::HANGING_SIGN);
|
||||
}
|
||||
|
||||
protected function getFacingDegrees() : float{
|
||||
return $this->rotation * 22.5;
|
||||
}
|
||||
}
|
@ -1,78 +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\block;
|
||||
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
use pocketmine\block\utils\HorizontalFacingOption;
|
||||
use pocketmine\block\utils\HorizontalFacingTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
|
||||
final class CeilingEdgesHangingSign extends BaseSign implements HorizontalFacing{
|
||||
use HorizontalFacingTrait;
|
||||
|
||||
protected function getSupportingFace() : Facing{
|
||||
return Facing::UP;
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if($face !== Facing::DOWN){
|
||||
return false;
|
||||
}
|
||||
if($player !== null){
|
||||
$this->facing = HorizontalFacingOption::fromFacing(Facing::opposite($player->getHorizontalFacing()));
|
||||
}
|
||||
if(!$this->canBeSupportedAt($blockReplace)){
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(!$this->canBeSupportedAt($this)){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
}
|
||||
}
|
||||
|
||||
private function canBeSupportedAt(Block $block) : bool{
|
||||
$supportBlock = $block->getSide(Facing::UP);
|
||||
return
|
||||
$supportBlock->getSupportType(Facing::DOWN) === SupportType::FULL ||
|
||||
(($supportBlock instanceof WallHangingSign || $supportBlock instanceof CeilingEdgesHangingSign) && Facing::axis($supportBlock->getFacing()->toFacing()) === Facing::axis($this->facing->toFacing()));
|
||||
}
|
||||
|
||||
protected function getFacingDegrees() : float{
|
||||
return match($this->facing){
|
||||
HorizontalFacingOption::SOUTH => 0,
|
||||
HorizontalFacingOption::WEST => 90,
|
||||
HorizontalFacingOption::NORTH => 180,
|
||||
HorizontalFacingOption::EAST => 270,
|
||||
};
|
||||
}
|
||||
}
|
@ -23,17 +23,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\PillarRotation;
|
||||
use pocketmine\block\utils\PillarRotationTrait;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\math\Axis;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
|
||||
final class Chain extends Transparent implements PillarRotation{
|
||||
final class Chain extends Transparent{
|
||||
use PillarRotationTrait;
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return $this->axis === Axis::Y && Facing::axis($facing) === Axis::Y ? SupportType::CENTER : SupportType::NONE;
|
||||
}
|
||||
|
||||
@ -41,7 +40,7 @@ final class Chain extends Transparent implements PillarRotation{
|
||||
$bb = AxisAlignedBB::one();
|
||||
foreach([Axis::Y, Axis::Z, Axis::X] as $axis){
|
||||
if($axis !== $this->axis){
|
||||
$bb = $bb->squashedCopy($axis, 13 / 32);
|
||||
$bb->squash($axis, 13 / 32);
|
||||
}
|
||||
}
|
||||
return [$bb];
|
||||
|
@ -24,16 +24,14 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
|
||||
final class ChemistryTable extends Opaque implements HorizontalFacing{
|
||||
final class ChemistryTable extends Opaque{
|
||||
use FacesOppositePlacingPlayerTrait;
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
//TODO
|
||||
return false;
|
||||
}
|
||||
|
@ -23,203 +23,76 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\inventory\window\BlockInventoryWindow;
|
||||
use pocketmine\block\inventory\window\DoubleChestInventoryWindow;
|
||||
use pocketmine\block\tile\Chest as TileChest;
|
||||
use pocketmine\block\utils\AnimatedContainerLike;
|
||||
use pocketmine\block\utils\AnimatedContainerLikeTrait;
|
||||
use pocketmine\block\utils\ChestPairHalf;
|
||||
use pocketmine\block\utils\Container;
|
||||
use pocketmine\block\utils\ContainerTrait;
|
||||
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
use pocketmine\block\utils\HorizontalFacingOption;
|
||||
use pocketmine\block\utils\SupportType;
|
||||
use pocketmine\event\block\ChestPairEvent;
|
||||
use pocketmine\inventory\CombinedInventoryProxy;
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\network\mcpe\protocol\BlockEventPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||
use pocketmine\player\InventoryWindow;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\Position;
|
||||
use pocketmine\world\sound\ChestCloseSound;
|
||||
use pocketmine\world\sound\ChestOpenSound;
|
||||
use pocketmine\world\sound\Sound;
|
||||
use function assert;
|
||||
|
||||
class Chest extends Transparent implements AnimatedContainerLike, Container, HorizontalFacing{
|
||||
use AnimatedContainerLikeTrait;
|
||||
use ContainerTrait;
|
||||
class Chest extends Transparent{
|
||||
use FacesOppositePlacingPlayerTrait;
|
||||
|
||||
protected ?ChestPairHalf $pairHalf = null;
|
||||
|
||||
public function getPairHalf() : ?ChestPairHalf{ return $this->pairHalf; }
|
||||
|
||||
public function setPairHalf(?ChestPairHalf $pairHalf) : self{
|
||||
$this->pairHalf = $pairHalf;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function readStateFromWorld() : Block{
|
||||
parent::readStateFromWorld();
|
||||
$tile = $this->position->getWorld()->getTile($this->position);
|
||||
|
||||
$this->pairHalf = null;
|
||||
if($tile instanceof TileChest && ($pairXZ = $tile->getPairXZ()) !== null){
|
||||
[$pairX, $pairZ] = $pairXZ;
|
||||
foreach(ChestPairHalf::cases() as $pairSide){
|
||||
$pairDirection = $pairSide->getOtherHalfSide($this->facing);
|
||||
$pairPosition = $this->position->getSide($pairDirection);
|
||||
if($pairPosition->getFloorX() === $pairX && $pairPosition->getFloorZ() === $pairZ){
|
||||
$this->pairHalf = $pairSide;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function writeStateToWorld() : void{
|
||||
parent::writeStateToWorld();
|
||||
$tile = $this->position->getWorld()->getTile($this->position);
|
||||
assert($tile instanceof TileChest);
|
||||
|
||||
//TODO: this should probably use relative coordinates instead of absolute, for portability
|
||||
if($this->pairHalf !== null){
|
||||
$pairDirection = $this->pairHalf->getOtherHalfSide($this->facing);
|
||||
$pairPosition = $this->position->getSide($pairDirection);
|
||||
$pairXZ = [$pairPosition->getFloorX(), $pairPosition->getFloorZ()];
|
||||
}else{
|
||||
$pairXZ = null;
|
||||
}
|
||||
$tile->setPairXZ($pairXZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
//these are slightly bigger than in PC
|
||||
$facing = $this->facing->toFacing();
|
||||
$box = AxisAlignedBB::one()
|
||||
->squashedCopy(Facing::axis($facing), 0.025)
|
||||
->trimmedCopy(Facing::UP, 0.05);
|
||||
$pairSide = $this->pairHalf?->getOtherHalfSide($this->facing);
|
||||
return [$pairSide !== null ?
|
||||
$box->trimmedCopy(Facing::opposite($pairSide), 0.025) :
|
||||
$box->squashedCopy(Facing::axis(Facing::rotateY($facing, true)), 0.025)
|
||||
];
|
||||
return [AxisAlignedBB::one()->contract(0.025, 0, 0.025)->trim(Facing::UP, 0.05)];
|
||||
}
|
||||
|
||||
public function getSupportType(Facing $facing) : SupportType{
|
||||
public function getSupportType(int $facing) : SupportType{
|
||||
return SupportType::NONE;
|
||||
}
|
||||
|
||||
private function getPossiblePair(ChestPairHalf $pairSide) : ?Chest{
|
||||
$pair = $this->getSide($pairSide->getOtherHalfSide($this->facing));
|
||||
return $pair->hasSameTypeId($this) && $pair instanceof Chest && $pair->getFacing() === $this->facing ? $pair : null;
|
||||
}
|
||||
|
||||
public function getOtherHalf() : ?Chest{
|
||||
return $this->pairHalf !== null && ($pair = $this->getPossiblePair($this->pairHalf)) !== null && $pair->pairHalf === $this->pairHalf->opposite() ? $pair : null;
|
||||
}
|
||||
|
||||
public function onPostPlace() : void{
|
||||
//Not sure if this vanilla behaviour is intended, but a chest facing north or west will try to pair on the left
|
||||
//side first, while a chest facing south or east will try the right side first.
|
||||
$order = match($this->facing){
|
||||
HorizontalFacingOption::NORTH, HorizontalFacingOption::WEST => [ChestPairHalf::LEFT, ChestPairHalf::RIGHT],
|
||||
HorizontalFacingOption::SOUTH, HorizontalFacingOption::EAST => [ChestPairHalf::RIGHT, ChestPairHalf::LEFT]
|
||||
};
|
||||
$world = $this->position->getWorld();
|
||||
foreach($order as $pairSide){
|
||||
$possiblePair = $this->getPossiblePair($pairSide);
|
||||
if($possiblePair !== null && $possiblePair->pairHalf === null){
|
||||
[$left, $right] = $pairSide === ChestPairHalf::LEFT ? [$this, $possiblePair] : [$possiblePair, $this];
|
||||
$ev = new ChestPairEvent($left, $right);
|
||||
if(!$ev->isCancelled() && $world->getBlock($this->position)->isSameState($this) && $world->getBlock($possiblePair->position)->isSameState($possiblePair)){
|
||||
$world->setBlock($this->position, $this->setPairHalf($pairSide));
|
||||
$world->setBlock($possiblePair->position, $possiblePair->setPairHalf($pairSide->opposite()));
|
||||
break;
|
||||
$tile = $world->getTile($this->position);
|
||||
if($tile instanceof TileChest){
|
||||
foreach([false, true] as $clockwise){
|
||||
$side = Facing::rotateY($this->facing, $clockwise);
|
||||
$c = $this->getSide($side);
|
||||
if($c instanceof Chest && $c->hasSameTypeId($this) && $c->facing === $this->facing){
|
||||
$pair = $world->getTile($c->position);
|
||||
if($pair instanceof TileChest && !$pair->isPaired()){
|
||||
[$left, $right] = $clockwise ? [$c, $this] : [$this, $c];
|
||||
$ev = new ChestPairEvent($left, $right);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled() && $world->getBlock($this->position)->hasSameTypeId($this) && $world->getBlock($c->position)->hasSameTypeId($c)){
|
||||
$pair->pairWith($tile);
|
||||
$tile->pairWith($pair);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
//TODO: If the pair chunk isn't loaded, a block update of an adjacent block in loaded terrain could cause the
|
||||
//chest to become unpaired. However, this is not unique to chests (think wall connections). Probably we
|
||||
//should defer updates in chunks whose neighbours are not loaded?
|
||||
if($this->pairHalf !== null && $this->getOtherHalf() === null){
|
||||
$this->position->getWorld()->setBlock($this->position, $this->setPairHalf(null));
|
||||
}
|
||||
}
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($player instanceof Player){
|
||||
|
||||
public function isOpeningObstructed() : bool{
|
||||
foreach([$this, $this->getOtherHalf()] as $chest){
|
||||
if($chest !== null && !$chest->getSide(Facing::UP)->isTransparent()){
|
||||
return true;
|
||||
$chest = $this->position->getWorld()->getTile($this->position);
|
||||
if($chest instanceof TileChest){
|
||||
if(
|
||||
!$this->getSide(Facing::UP)->isTransparent() ||
|
||||
(($pair = $chest->getPair()) !== null && !$pair->getBlock()->getSide(Facing::UP)->isTransparent()) ||
|
||||
!$chest->canOpenWith($item->getCustomName())
|
||||
){
|
||||
return true;
|
||||
}
|
||||
|
||||
$player->setCurrentWindow($chest->getInventory());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getTile() : ?TileChest{
|
||||
$tile = $this->position->getWorld()->getTile($this->position);
|
||||
return $tile instanceof TileChest ? $tile : null;
|
||||
}
|
||||
|
||||
public function getInventory() : ?Inventory{
|
||||
$thisInventory = $this->getTile()?->getRealInventory();
|
||||
if($thisInventory === null){
|
||||
return null;
|
||||
}
|
||||
$pairInventory = $this->getOtherHalf()?->getTile()?->getRealInventory();
|
||||
if($pairInventory === null){
|
||||
return $thisInventory;
|
||||
}
|
||||
|
||||
[$left, $right] = $this->pairHalf === ChestPairHalf::LEFT ? [$thisInventory, $pairInventory] : [$pairInventory, $thisInventory];
|
||||
return new CombinedInventoryProxy([$left, $right]);
|
||||
}
|
||||
|
||||
protected function newMenu(Player $player, Inventory $inventory, Position $position) : InventoryWindow{
|
||||
$pair = $this->getOtherHalf();
|
||||
if($pair === null){
|
||||
return new BlockInventoryWindow($player, $inventory, $position);
|
||||
}
|
||||
[$left, $right] = $this->pairHalf === ChestPairHalf::LEFT ? [$this, $pair] : [$pair, $this];
|
||||
return new DoubleChestInventoryWindow($player, $inventory, $left->position, $right->position);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFuelTime() : int{
|
||||
return 300;
|
||||
}
|
||||
|
||||
protected function getOpenSound() : Sound{
|
||||
return new ChestOpenSound();
|
||||
}
|
||||
|
||||
protected function getCloseSound() : Sound{
|
||||
return new ChestCloseSound();
|
||||
}
|
||||
|
||||
protected function playAnimationVisual(Position $position, bool $isOpen) : void{
|
||||
//event ID is always 1 for a chest
|
||||
//TODO: we probably shouldn't be sending a packet directly here, but it doesn't fit anywhere into existing systems
|
||||
$position->getWorld()->broadcastPacketToViewers($position, BlockEventPacket::create(BlockPosition::fromVector3($position), 1, $isOpen ? 1 : 0));
|
||||
}
|
||||
|
||||
protected function doAnimationEffects(bool $isOpen) : void{
|
||||
$this->playAnimationVisual($this->position, $isOpen);
|
||||
$this->playAnimationSound($this->position, $isOpen);
|
||||
|
||||
$pair = $this->getOtherHalf();
|
||||
if($pair !== null){
|
||||
$this->playAnimationVisual($pair->position, $isOpen);
|
||||
$this->playAnimationSound($pair->position, $isOpen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ namespace pocketmine\block;
|
||||
use pocketmine\block\tile\ChiseledBookshelf as TileChiseledBookshelf;
|
||||
use pocketmine\block\utils\ChiseledBookshelfSlot;
|
||||
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
use pocketmine\block\utils\HorizontalFacingTrait;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\item\Book;
|
||||
@ -39,7 +38,7 @@ use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use function spl_object_id;
|
||||
|
||||
class ChiseledBookshelf extends Opaque implements HorizontalFacing{
|
||||
class ChiseledBookshelf extends Opaque{
|
||||
use HorizontalFacingTrait;
|
||||
use FacesOppositePlacingPlayerTrait;
|
||||
|
||||
@ -52,7 +51,7 @@ class ChiseledBookshelf extends Opaque implements HorizontalFacing{
|
||||
private ?ChiseledBookshelfSlot $lastInteractedSlot = null;
|
||||
|
||||
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||
$w->enum($this->facing);
|
||||
$w->horizontalFacing($this->facing);
|
||||
$w->enumSet($this->slots, ChiseledBookshelfSlot::cases());
|
||||
}
|
||||
|
||||
@ -114,18 +113,6 @@ class ChiseledBookshelf extends Opaque implements HorizontalFacing{
|
||||
return $this->slots;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ChiseledBookshelfSlot[] $slots
|
||||
* @return $this
|
||||
*/
|
||||
public function setSlots(array $slots) : self{
|
||||
$this->slots = [];
|
||||
foreach($slots as $slot){
|
||||
$this->setSlot($slot, true);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last slot interacted by a player or null if no slot has been interacted with yet.
|
||||
*/
|
||||
@ -143,8 +130,8 @@ class ChiseledBookshelf extends Opaque implements HorizontalFacing{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($face !== $this->facing->toFacing()){
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($face !== $this->facing){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\Ageable;
|
||||
use pocketmine\block\utils\AgeableTrait;
|
||||
use pocketmine\block\utils\StaticSupportTrait;
|
||||
use pocketmine\entity\projectile\Projectile;
|
||||
@ -41,7 +40,7 @@ use function array_rand;
|
||||
use function min;
|
||||
use function mt_rand;
|
||||
|
||||
final class ChorusFlower extends Flowable implements Ageable{
|
||||
final class ChorusFlower extends Flowable{
|
||||
use AgeableTrait;
|
||||
use StaticSupportTrait;
|
||||
|
||||
@ -106,9 +105,9 @@ final class ChorusFlower extends Flowable implements Ageable{
|
||||
return [$stemHeight, $endStoneBelow];
|
||||
}
|
||||
|
||||
private function allHorizontalBlocksEmpty(World $world, Vector3 $position, ?Facing $except) : bool{
|
||||
private function allHorizontalBlocksEmpty(World $world, Vector3 $position, ?int $except) : bool{
|
||||
foreach($position->sidesAroundAxis(Axis::Y) as $facing => $sidePosition){
|
||||
if($facing === $except?->value){
|
||||
if($facing === $except){
|
||||
continue;
|
||||
}
|
||||
if($world->getBlock($sidePosition)->getTypeId() !== BlockTypeIds::AIR){
|
||||
@ -149,7 +148,7 @@ final class ChorusFlower extends Flowable implements Ageable{
|
||||
return $this->allHorizontalBlocksEmpty($world, $up, null);
|
||||
}
|
||||
|
||||
private function grow(Facing $facing, int $ageChange, ?BlockTransaction $tx) : BlockTransaction{
|
||||
private function grow(int $facing, int $ageChange, ?BlockTransaction $tx) : BlockTransaction{
|
||||
if($tx === null){
|
||||
$tx = new BlockTransaction($this->position->getWorld());
|
||||
}
|
||||
@ -176,10 +175,10 @@ final class ChorusFlower extends Flowable implements Ageable{
|
||||
$facingVisited = [];
|
||||
for($attempts = 0, $maxAttempts = mt_rand(0, $endStoneBelow ? 4 : 3); $attempts < $maxAttempts; $attempts++){
|
||||
$facing = Facing::HORIZONTAL[array_rand(Facing::HORIZONTAL)];
|
||||
if(isset($facingVisited[$facing->value])){
|
||||
if(isset($facingVisited[$facing])){
|
||||
continue;
|
||||
}
|
||||
$facingVisited[$facing->value] = true;
|
||||
$facingVisited[$facing] = true;
|
||||
|
||||
$sidePosition = $this->position->getSide($facing);
|
||||
if(
|
||||
|
@ -34,43 +34,18 @@ 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(Facing::ALL as $facing){
|
||||
if(!isset($this->connections[$facing->value])){
|
||||
$bb = $bb->trimmedCopy($facing, 2 / 16);
|
||||
foreach($this->getAllSides() as $facing => $block){
|
||||
$id = $block->getTypeId();
|
||||
if($id !== BlockTypeIds::END_STONE && $id !== BlockTypeIds::CHORUS_FLOWER && !$block->hasSameTypeId($this)){
|
||||
$bb->trim($facing, 2 / 16);
|
||||
}
|
||||
}
|
||||
|
||||
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->value] = true;
|
||||
}else{
|
||||
unset($this->connections[$facing->value]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function canBeSupportedBy(Block $block) : bool{
|
||||
return $block->hasSameTypeId($this) || $block->getTypeId() === BlockTypeIds::END_STONE;
|
||||
}
|
||||
|
@ -23,17 +23,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\Ageable;
|
||||
use pocketmine\block\utils\AgeableTrait;
|
||||
use pocketmine\block\utils\BlockEventHelper;
|
||||
use pocketmine\block\utils\HorizontalFacing;
|
||||
use pocketmine\block\utils\HorizontalFacingOption;
|
||||
use pocketmine\block\utils\HorizontalFacingTrait;
|
||||
use pocketmine\block\utils\WoodType;
|
||||
use pocketmine\data\runtime\RuntimeDataDescriber;
|
||||
use pocketmine\item\Fertilizer;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\item\VanillaItems;
|
||||
use pocketmine\math\Axis;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
@ -41,26 +39,28 @@ use pocketmine\player\Player;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use function mt_rand;
|
||||
|
||||
class CocoaBlock extends Flowable implements Ageable, HorizontalFacing{
|
||||
class CocoaBlock extends Flowable{
|
||||
use HorizontalFacingTrait;
|
||||
use AgeableTrait;
|
||||
|
||||
public const MAX_AGE = 2;
|
||||
|
||||
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
|
||||
$w->enum($this->facing);
|
||||
$w->horizontalFacing($this->facing);
|
||||
$w->boundedIntAuto(0, self::MAX_AGE, $this->age);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
$realFacing = $this->facing->toFacing();
|
||||
return [
|
||||
AxisAlignedBB::one()
|
||||
->squashedCopy(Facing::axis(Facing::rotateY($realFacing, true)), (6 - $this->age) / 16) //sides
|
||||
->trimmedCopy(Facing::DOWN, (7 - $this->age * 2) / 16)
|
||||
->trimmedCopy(Facing::UP, 0.25)
|
||||
->trimmedCopy(Facing::opposite($realFacing), 1 / 16) //gap between log and pod
|
||||
->trimmedCopy($realFacing, (11 - $this->age * 2) / 16) //outward face
|
||||
->squash(Facing::axis(Facing::rotateY($this->facing, true)), (6 - $this->age) / 16) //sides
|
||||
->trim(Facing::DOWN, (7 - $this->age * 2) / 16)
|
||||
->trim(Facing::UP, 0.25)
|
||||
->trim(Facing::opposite($this->facing), 1 / 16) //gap between log and pod
|
||||
->trim($this->facing, (11 - $this->age * 2) / 16) //outward face
|
||||
];
|
||||
}
|
||||
|
||||
@ -68,16 +68,16 @@ class CocoaBlock extends Flowable implements Ageable, HorizontalFacing{
|
||||
return $block instanceof Wood && $block->getWoodType() === WoodType::JUNGLE;
|
||||
}
|
||||
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, Facing $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(($hzFacing = HorizontalFacingOption::tryFromFacing($face)) !== null && $this->canAttachTo($blockClicked)){
|
||||
$this->facing = $hzFacing;
|
||||
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
|
||||
if(Facing::axis($face) !== Axis::Y && $this->canAttachTo($blockClicked)){
|
||||
$this->facing = $face;
|
||||
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function onInteract(Item $item, Facing $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
|
||||
if($item instanceof Fertilizer && $this->grow($player)){
|
||||
$item->pop();
|
||||
|
||||
@ -88,7 +88,7 @@ class CocoaBlock extends Flowable implements Ageable, HorizontalFacing{
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
if(!$this->canAttachTo($this->getSide(Facing::opposite($this->facing->toFacing())))){
|
||||
if(!$this->canAttachTo($this->getSide(Facing::opposite($this->facing)))){
|
||||
$this->position->getWorld()->useBreakOn($this->position);
|
||||
}
|
||||
}
|
||||
|
@ -23,9 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\block;
|
||||
|
||||
use pocketmine\block\utils\Colored;
|
||||
use pocketmine\block\utils\ColoredTrait;
|
||||
|
||||
class Concrete extends Opaque implements Colored{
|
||||
class Concrete extends Opaque{
|
||||
use ColoredTrait;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user