Compare commits

..

101 Commits

Author SHA1 Message Date
897816c6bb this has no business being public 2024-12-06 14:55:07 +00:00
f93333227c Merge branch 'major-next' into block-position 2024-12-06 14:46:58 +00:00
6d2329128a Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12191303914
2024-12-06 01:38:37 +00:00
07045dd424 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12184052098
2024-12-05 16:35:59 +00:00
c5b0df4578 Merge remote-tracking branch 'origin/minor-next' into major-next 2024-12-05 16:07:28 +00:00
5e9dbace90 Merge branch 'minor-next' into major-next 2024-12-05 10:13:24 +00:00
8245fa35f6 ... 2024-12-04 16:26:40 +00:00
241e356780 ... 2024-12-04 16:22:45 +00:00
39662bdc3b moar changes 2024-12-04 16:13:21 +00:00
224fa09327 First step towards removing Vector3 from Block 2024-12-04 15:54:31 +00:00
205aabe11f Fixed merge error 2024-12-04 15:27:09 +00:00
3091e1325f Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12151373979
2024-12-04 01:39:59 +00:00
779e80a961 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12131296321
2024-12-03 01:39:33 +00:00
007673cb96 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12111121061
2024-12-02 01:41:02 +00:00
0dae786a21 feat(Server): add a setter for maxPlayers (#6261) 2024-12-01 20:24:50 +00:00
02d181d0c8 Merge branch 'minor-next' into major-next 2024-12-01 15:02:36 +00:00
2fc6bbe84e Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12092463227
2024-11-30 01:35:08 +00:00
002383be89 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12077579940
2024-11-29 01:37:53 +00:00
00bdb6be73 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12060967026
2024-11-28 01:37:50 +00:00
c3c917bb05 Merge branch 'minor-next' into major-next 2024-11-27 17:56:45 +00:00
a078f653f4 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12042121495
2024-11-27 01:39:13 +00:00
ed33983792 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12022106146
2024-11-26 01:36:42 +00:00
15eaf67a0c Merge branch 'minor-next' into major-next 2024-11-25 14:36:25 +00:00
d72941c36c Update IceBomb.php 2024-11-24 23:56:44 +00:00
e51903d7ea Merge branch 'minor-next' into major-next 2024-11-24 23:51:07 +00:00
3e9a96b43a Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11990667732
2024-11-23 21:49:02 +00:00
9fce27eaa8 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11990103798
2024-11-23 20:14:24 +00:00
7208733d62 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11989715327
2024-11-23 19:10:14 +00:00
c61434d87b Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11964819241
2024-11-22 01:36:17 +00:00
dcc258706f Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11944832380
2024-11-21 01:27:14 +00:00
820e2d4a2f Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11924942897
2024-11-20 01:27:32 +00:00
0fb1415f7f Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11904511045
2024-11-19 01:36:12 +00:00
a6534ecbbb Fixed merge error 2024-11-17 01:55:46 +00:00
330bcd2423 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11874902000
2024-11-17 01:39:53 +00:00
e71b9e8dc6 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11866065217
2024-11-16 01:37:29 +00:00
9e2d91bae6 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11865975725
2024-11-16 01:26:32 +00:00
b6f55b78a9 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11862895039
2024-11-15 20:21:33 +00:00
ab5176baf9 Merge branch 'minor-next' into major-next 2024-11-14 23:16:44 +00:00
ef6fce4091 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11824307499
2024-11-13 19:21:19 +00:00
cc335889f3 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11802296296
2024-11-12 17:14:26 +00:00
80b7f6aba4 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11769186885
2024-11-10 22:56:09 +00:00
82c5a3160c Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11652590245
2024-11-03 15:33:37 +00:00
85de28d6c3 Merge branch 'minor-next' into major-next 2024-11-03 14:02:06 +00:00
1ef854f2d1 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11392123557
2024-10-17 19:59:15 +00:00
082af9978c Merge branch 'minor-next' into major-next 2024-09-23 15:08:45 -05:00
e8620ef94d Restore travis.sh execute permission 2024-09-22 21:59:40 -05:00
83a91634c3 Merge branch 'minor-next' into sync-major-next 2024-09-22 21:44:31 -05:00
3c73bd22dd Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/10458521667
2024-08-19 18:03:25 +00:00
0e1824451b Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/10442250915
2024-08-18 16:50:43 +00:00
6c5ae634fd Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/10373700926
2024-08-13 16:37:46 +00:00
041944ed16 Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/10371827817
2024-08-13 14:36:02 +00:00
603527c6e8 Merge branch 'minor-next' of github.com:pmmp/PocketMine-MP into major-next 2024-08-13 13:39:00 +00:00
1ac08ea73b Remove enchant properties that no longer exists on PM6 (#6417) 2024-08-10 08:51:06 -05:00
c9e8d382c5 Merge branch 'minor-next' of github.com:pmmp/PocketMine-MP into major-next 2024-08-09 13:33:07 +01:00
12179aa03a Merge branch 'minor-next' into major-next 2024-05-06 15:42:49 +01:00
e781c64540 Merge branch 'minor-next' into major-next 2024-03-14 12:47:18 +00:00
644693ffee Merge branch 'minor-next' into major-next 2024-02-12 11:55:36 +00:00
6b66cbfb1c Merge branch 'minor-next' into major-next 2023-12-20 15:26:57 +00:00
4d337add7c Merge branch 'minor-next' into major-next 2023-12-14 14:03:09 +00:00
9d75c45bf5 Merge branch 'minor-next' into major-next 2023-11-09 18:06:06 +00:00
c7a537abbb Merge branch 'minor-next' into major-next 2023-11-01 18:03:04 +00:00
54694df48c Merge branch 'minor-next' into major-next 2023-11-01 16:39:55 +00:00
15aae721cd Merge branch 'minor-next' into major-next 2023-10-26 12:57:24 +01:00
d565be93a8 Merge branch 'minor-next' into major-next 2023-10-24 11:57:30 +01:00
e32a90be72 Make Player->getFirstPlayed() and Player->getLastPlayed() return DateTimeImmutable (#6042) 2023-10-17 16:26:21 +01:00
d4d7d02067 Merge branch 'minor-next' into major-next 2023-10-16 21:29:11 +01:00
a45e143e81 Merge branch 'minor-next' into major-next 2023-09-21 13:29:58 +01:00
05981d2669 Merge branch 'minor-next' into major-next 2023-09-20 19:15:08 +01:00
fa9bba470c RegistryTrait: use native parameter types for __callStatic() arguments (#5944) 2023-09-08 12:03:03 +01:00
361626d236 Merge branch 'minor-next' into major-next 2023-09-08 11:27:09 +01:00
16d8522245 Farewell EnumTrait, you served us well 2023-09-08 11:26:11 +01:00
a4f3476190 Merge branch 'minor-next' into major-next 2023-09-08 11:22:44 +01:00
e96e68d221 Merge branch 'minor-next' into major-next 2023-09-07 20:33:35 +01:00
f1a6d71cc1 Merge branch 'minor-next' into major-next 2023-09-07 20:30:58 +01:00
89f42c80d4 Strip out deprecated stuff 2023-09-07 20:26:04 +01:00
cd6b780d31 Merge branch 'minor-next' into major-next 2023-09-07 20:10:31 +01:00
ed61a68013 Entity: make getNetworkTypeId non-static (#6037)
This was static to permit ItemFactory to register spawn eggs for all known entity types in early PM4. However, nowadays we provide a callback to the spawn egg instead, and spawn eggs must be manually implemented, so this is no longer needed.

In addition, having this static forces everyone to make a new entity class for every unique type of entity, which isn't ideal.
2023-09-06 15:26:32 +01:00
4dc9d696d0 Merge branch 'minor-next' into major-next 2023-09-06 13:03:51 +01:00
258038c9a9 Merge branch 'minor-next' into major-next 2023-08-21 16:08:32 +01:00
5c915a3dfe Merge branch 'minor-next' into major-next 2023-08-18 12:33:54 +01:00
8c594fd126 Merge branch 'minor-next' into major-next 2023-08-15 17:41:50 +01:00
9fd6653f36 Remove deprecated APIs 2023-08-09 16:38:09 +01:00
32d67080e5 Merge branch 'minor-next' into major-next 2023-08-09 16:35:32 +01:00
ed9d057ca2 Merge branch 'minor-next' into major-next 2023-08-08 18:27:58 +01:00
5ec0e0f20b Merge branch 'minor-next' into major-next 2023-08-08 17:48:23 +01:00
cb251069dd PluginLoader: rename parameters 2023-08-07 16:39:54 +01:00
e0ad39b70a Remove ResourceProvider cruft
this had no obvious reason for existing, and with #5958 looming, this will become altogether useless anyway.
2023-08-07 16:14:01 +01:00
9997b614bc Merge branch 'minor-next' into major-next 2023-08-01 12:53:53 +01:00
89f8f421a6 Server: stop discriminating against folder plugins when generating crashdumps
in PM6, non-development plugins may appear in folder form.
2023-07-26 10:47:39 +01:00
c4ff6d7757 Merge branch 'minor-next' into major-next 2023-07-24 16:45:55 +01:00
3c0e7ae492 Merge branch 'minor-next' into major-next 2023-07-24 12:07:55 +01:00
b944205f60 Remove useless checks for plugins disabling other plugins (#5931) 2023-07-24 11:31:19 +01:00
2ab3393568 Unlink DevTools submodule and remove references
we don't need this as a submodule anymore, since it's not used in the core.
2023-07-20 11:29:06 +01:00
1e1b95e1b8 uh oh 2023-07-19 18:06:44 +01:00
62465fa676 Integrate FolderPluginLoader
the motivation for this is described in #5917

a new version of DevTools will be required, as the current version will cause the server to abort during startup with this change due to duplicated plugin loading.
2023-07-19 18:05:41 +01:00
aac5944396 Accept Translatable permission messages in Command (#5830) 2023-07-19 16:38:15 +01:00
74cfd687d7 CraftingManagerFromDataHelper: Fix parameter name typo (#5870)
due to named parameters, this change must target PM6
2023-07-19 11:05:52 +01:00
f2f30143b0 Merge branch 'minor-next' into major-next 2023-07-18 22:22:43 +01:00
d98adf127f Merge branch 'minor-next' into major-next 2023-07-17 16:13:28 +01:00
280bf60830 Merge branch 'minor-next' into major-next 2023-07-14 13:28:07 +01:00
1ffa945fbf Disallow plugins disabling other plugins (#5872) 2023-07-13 13:44:28 +01:00
356 changed files with 2290 additions and 5968 deletions

View 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

View File

@ -1,84 +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
- 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
View 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 -->

16
.github/ISSUE_TEMPLATE/crash.md vendored Normal file
View 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)

View File

@ -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

View File

@ -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"

View File

@ -53,7 +53,7 @@ jobs:
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
- name: Build image for tag
uses: docker/build-push-action@v6.13.0
uses: docker/build-push-action@v6.10.0
with:
push: true
context: ./pocketmine-mp
@ -66,7 +66,7 @@ jobs:
- name: Build image for major tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v6.13.0
uses: docker/build-push-action@v6.10.0
with:
push: true
context: ./pocketmine-mp
@ -79,7 +79,7 @@ jobs:
- name: Build image for minor tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v6.13.0
uses: docker/build-push-action@v6.10.0
with:
push: true
context: ./pocketmine-mp
@ -92,7 +92,7 @@ jobs:
- name: Build image for latest tag
if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v6.13.0
uses: docker/build-push-action@v6.10.0
with:
push: true
context: ./pocketmine-mp

View File

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.32.0
uses: shivammathur/setup-php@2.31.1
with:
php-version: 8.2

View 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 }}"}'

View 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

View File

@ -49,7 +49,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@2.32.0
uses: shivammathur/setup-php@2.31.1
with:
php-version: 8.2

View File

@ -1,85 +1,28 @@
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.2"
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-20.04
outputs:
skip: ${{ steps.exists.outputs.exists == 'true' }}
steps:
- name: Check if release already exists
id: exists
env:
GH_TOKEN: ${{ github.token }}
run: |
exists=false
if [[ "${{ github.ref_type }}" == "tag" ]]; then
tag="$(echo "${{ github.ref }}" | cut -d/ -f3-)"
if gh release view "$tag" --repo "${{ github.repository }}"; then
exists=true
fi
fi
echo exists=$exists >> $GITHUB_OUTPUT
check:
needs: [skip]
if: needs.skip.outputs.skip != 'true'
name: Check release
uses: ./.github/workflows/draft-release-pr-check.yml
trigger-post-release-workflow:
name: Trigger post-release RestrictedActions workflow
needs: [check]
if: needs.check.outputs.valid == 'true' && github.ref_type != 'tag' #can't do post-commit for a tag
runs-on: ubuntu-20.04
steps:
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
owner: ${{ github.repository_owner }}
repositories: RestrictedActions
- name: Dispatch post-release restricted action
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ steps.generate-token.outputs.token }}
repository: ${{ github.repository_owner }}/RestrictedActions
event-type: pocketmine_mp_post_release
client-payload: '{"branch": "${{ github.ref }}"}'
draft:
name: Create GitHub draft release
needs: [check]
if: needs.check.outputs.valid == 'true' || github.ref_type == 'tag' #ignore validity check for tags
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
php-version: [8.2]
outputs:
draft-url: ${{ steps.create-draft.outputs.html_url }}
version: ${{ steps.get-pm-version.outputs.PM_VERSION }}
steps:
- uses: actions/checkout@v4
@ -87,9 +30,9 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.32.0
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.15.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,17 +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 }}).
- name: Post draft release URL on PR
if: github.event_name == 'pull_request_target'
uses: thollander/actions-comment-pull-request@v3
with:
message: "[Draft release ${{ steps.get-pm-version.outputs.PM_VERSION }}](${{ steps.create-draft.outputs.html_url }}) has been created for commit ${{ github.sha }}. Please review and publish it."

View File

@ -28,7 +28,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.32.0
uses: shivammathur/setup-php@2.31.1
with:
php-version: 8.2
tools: php-cs-fixer:3.49

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[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

View File

@ -5,7 +5,6 @@ $finder = PhpCsFixer\Finder::create()
->in(__DIR__ . '/build')
->in(__DIR__ . '/tests')
->in(__DIR__ . '/tools')
->notPath('plugins/DevTools')
->notName('PocketMine.php');
return (new PhpCsFixer\Config)

View File

@ -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
View 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();

View File

@ -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';

View File

@ -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);
}

View File

@ -114,26 +114,3 @@ Released 5th December 2024.
## Internals
- Improved blockstate consistency check to detect tiles disappearing during refactors.
# 5.23.2
Released 9th December 2024.
## General
- Updated translations for Russian and Korean.
## Fixes
- Fixed server build number.
- Fixed some crashes being misreported as plugin-involved.
## Internals
- Removed legacy `build/make-release.php` script. This script is no longer used, as all releases should now follow the PR workflow.
# 5.23.3
Released 22nd January 2025.
## Fixes
- Fixed crashes with PHP internal stack frames being flagged as plugin crashes.
- Fixed note block instrument sounds in 1.21.50.
## Internals
- Updated GitHub issue templates to use issue forms.

View File

@ -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.

View File

@ -1,46 +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.

View File

@ -33,15 +33,15 @@
"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": "~4.0.0+bedrock-1.21.60",
"pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
"pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50",
"pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
"pocketmine/bedrock-protocol": "~36.0.0+bedrock-1.21.60",
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.50",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0",
"pocketmine/errorhandler": "^0.7.0",
"pocketmine/locale-data": "~2.24.0",
"pocketmine/locale-data": "~2.22.0",
"pocketmine/log": "^0.4.0",
"pocketmine/math": "~1.0.0",
"pocketmine/nbt": "~1.0.0",
@ -52,9 +52,9 @@
"symfony/filesystem": "~6.4.0"
},
"require-dev": {
"phpstan/phpstan": "2.1.6",
"phpstan/phpstan-phpunit": "^2.0.0",
"phpstan/phpstan-strict-rules": "^2.0.0",
"phpstan/phpstan": "1.11.11",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "^10.5.24"
},
"autoload": {
@ -78,7 +78,6 @@
"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"

178
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "bef9decc40d9f5bd82e1de2d151bd99f",
"content-hash": "732102eca72dc1d29e7b67dfbce07653",
"packages": [
{
"name": "adhocore/json-comment",
@ -178,16 +178,16 @@
},
{
"name": "pocketmine/bedrock-block-upgrade-schema",
"version": "5.1.0",
"version": "5.0.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
"reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a"
"reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/2218512e4b91f5bfd09ef55f7a4c4b04e169e41a",
"reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
"reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
"shasum": ""
},
"type": "library",
@ -198,22 +198,22 @@
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
"support": {
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.1.0"
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.0.0"
},
"time": "2025-02-11T17:41:44+00:00"
"time": "2024-11-03T14:13:50+00:00"
},
{
"name": "pocketmine/bedrock-data",
"version": "4.0.0+bedrock-1.21.60",
"version": "2.15.0+bedrock-1.21.50",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e"
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/2e5f16ec2facac653f3f894f22eb831d880ba98e",
"reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
"shasum": ""
},
"type": "library",
@ -224,9 +224,9 @@
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/BedrockData/issues",
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.60"
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.50"
},
"time": "2025-02-16T15:56:56+00:00"
"time": "2024-12-04T12:59:12+00:00"
},
{
"name": "pocketmine/bedrock-item-upgrade-schema",
@ -256,16 +256,16 @@
},
{
"name": "pocketmine/bedrock-protocol",
"version": "36.0.0+bedrock-1.21.60",
"version": "35.0.0+bedrock-1.21.50",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "2057de319c5c551001c2a544e08d1bc7727d9963"
"reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2057de319c5c551001c2a544e08d1bc7727d9963",
"reference": "2057de319c5c551001c2a544e08d1bc7727d9963",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
"reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
"shasum": ""
},
"require": {
@ -278,10 +278,10 @@
"ramsey/uuid": "^4.1"
},
"require-dev": {
"phpstan/phpstan": "2.1.0",
"phpstan/phpstan-phpunit": "^2.0.0",
"phpstan/phpstan-strict-rules": "^2.0.0",
"phpunit/phpunit": "^9.5 || ^10.0 || ^11.0"
"phpstan/phpstan": "1.11.9",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5 || ^10.0"
},
"type": "library",
"autoload": {
@ -296,9 +296,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/36.0.0+bedrock-1.21.60"
"source": "https://github.com/pmmp/BedrockProtocol/tree/35.0.0+bedrock-1.21.50"
},
"time": "2025-02-16T15:59:08+00:00"
"time": "2024-12-04T13:02:00+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -471,16 +471,16 @@
},
{
"name": "pocketmine/locale-data",
"version": "2.24.0",
"version": "2.22.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Language.git",
"reference": "6ec5e92c77a2102b2692763733e4763012facae9"
"reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/6ec5e92c77a2102b2692763733e4763012facae9",
"reference": "6ec5e92c77a2102b2692763733e4763012facae9",
"url": "https://api.github.com/repos/pmmp/Language/zipball/aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
"reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
"shasum": ""
},
"type": "library",
@ -488,9 +488,9 @@
"description": "Language resources used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Language/issues",
"source": "https://github.com/pmmp/Language/tree/2.24.0"
"source": "https://github.com/pmmp/Language/tree/2.22.0"
},
"time": "2025-02-16T20:46:34+00:00"
"time": "2024-11-16T13:28:01+00:00"
},
{
"name": "pocketmine/log",
@ -576,16 +576,16 @@
},
{
"name": "pocketmine/nbt",
"version": "1.0.1",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d"
"reference": "20540271cb59e04672cb163dca73366f207974f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/53db37487bc5ddbfbd84247966e1a073bdcfdb7d",
"reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/20540271cb59e04672cb163dca73366f207974f1",
"reference": "20540271cb59e04672cb163dca73366f207974f1",
"shasum": ""
},
"require": {
@ -595,8 +595,8 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "2.1.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpstan/phpstan": "1.10.25",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5"
},
"type": "library",
@ -612,9 +612,9 @@
"description": "PHP library for working with Named Binary Tags",
"support": {
"issues": "https://github.com/pmmp/NBT/issues",
"source": "https://github.com/pmmp/NBT/tree/1.0.1"
"source": "https://github.com/pmmp/NBT/tree/1.0.0"
},
"time": "2025-01-07T22:47:46+00:00"
"time": "2023-07-14T13:01:49+00:00"
},
{
"name": "pocketmine/raklib",
@ -1013,8 +1013,8 @@
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
@ -1092,8 +1092,8 @@
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
@ -1150,16 +1150,16 @@
"packages-dev": [
{
"name": "myclabs/deep-copy",
"version": "1.13.0",
"version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "024473a478be9df5fdaca2c793f2232fe788e414"
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
"reference": "024473a478be9df5fdaca2c793f2232fe788e414",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
"shasum": ""
},
"require": {
@ -1198,7 +1198,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
},
"funding": [
{
@ -1206,20 +1206,20 @@
"type": "tidelift"
}
],
"time": "2025-02-12T12:17:51+00:00"
"time": "2024-11-08T17:47:46+00:00"
},
{
"name": "nikic/php-parser",
"version": "v5.4.0",
"version": "v5.3.1",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "447a020a1f875a434d62f2a401f53b82a396e494"
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494",
"reference": "447a020a1f875a434d62f2a401f53b82a396e494",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b",
"reference": "8eea230464783aa9671db8eea6f8c6ac5285794b",
"shasum": ""
},
"require": {
@ -1262,9 +1262,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1"
},
"time": "2024-12-30T11:07:19+00:00"
"time": "2024-10-08T18:51:32+00:00"
},
{
"name": "phar-io/manifest",
@ -1386,20 +1386,20 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.6",
"version": "1.11.11",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c"
"reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c",
"reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3",
"reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0"
"php": "^7.2|^8.0"
},
"conflict": {
"phpstan/phpstan-shim": "*"
@ -1440,33 +1440,34 @@
"type": "github"
}
],
"time": "2025-02-19T15:46:42+00:00"
"time": "2024-08-19T14:37:29+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
"version": "2.0.4",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
"reference": "d09e152f403c843998d7a52b5d87040c937525dd"
"reference": "f3ea021866f4263f07ca3636bf22c64be9610c11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d09e152f403c843998d7a52b5d87040c937525dd",
"reference": "d09e152f403c843998d7a52b5d87040c937525dd",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f3ea021866f4263f07ca3636bf22c64be9610c11",
"reference": "f3ea021866f4263f07ca3636bf22c64be9610c11",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"phpstan/phpstan": "^2.0.4"
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.11"
},
"conflict": {
"phpunit/phpunit": "<7.0"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^9.6"
"phpstan/phpstan-strict-rules": "^1.5.1",
"phpunit/phpunit": "^9.5"
},
"type": "phpstan-extension",
"extra": {
@ -1489,33 +1490,34 @@
"description": "PHPUnit extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
"source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.4"
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.4.0"
},
"time": "2025-01-22T13:07:38+00:00"
"time": "2024-04-20T06:39:00+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "2.0.3",
"version": "1.6.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba"
"reference": "363f921dd8441777d4fc137deb99beb486c77df1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba",
"reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1",
"reference": "363f921dd8441777d4fc137deb99beb486c77df1",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"phpstan/phpstan": "^2.0.4"
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.11"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^9.6"
"phpstan/phpstan-deprecation-rules": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^9.5"
},
"type": "phpstan-extension",
"extra": {
@ -1537,9 +1539,9 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0"
},
"time": "2025-01-21T10:52:14+00:00"
"time": "2024-04-20T06:37:51+00:00"
},
{
"name": "phpunit/php-code-coverage",
@ -1864,16 +1866,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.45",
"version": "10.5.38",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "bd68a781d8e30348bc297449f5234b3458267ae8"
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bd68a781d8e30348bc297449f5234b3458267ae8",
"reference": "bd68a781d8e30348bc297449f5234b3458267ae8",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132",
"reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132",
"shasum": ""
},
"require": {
@ -1883,7 +1885,7 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.12.1",
"myclabs/deep-copy": "^1.12.0",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.1",
@ -1945,7 +1947,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.45"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38"
},
"funding": [
{
@ -1961,7 +1963,7 @@
"type": "tidelift"
}
],
"time": "2025-02-06T16:08:12+00:00"
"time": "2024-10-28T13:06:21+00:00"
},
{
"name": "sebastian/cli-parser",
@ -2967,5 +2969,5 @@
"platform-overrides": {
"php": "8.1.0"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}

View File

@ -10,8 +10,6 @@ includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
rules:
- pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule
- pocketmine\phpstan\rules\DisallowEnumComparisonRule
- pocketmine\phpstan\rules\DisallowForeachByReferenceRule
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule
@ -19,7 +17,6 @@ rules:
parameters:
level: 9
checkMissingCallableSignature: true
rememberPossiblyImpureFunctionValues: false #risky to remember these, better for performance to avoid repeated calls anyway
treatPhpDocTypesAsCertain: false
bootstrapFiles:
- tests/phpstan/bootstrap.php
@ -32,7 +29,6 @@ parameters:
paths:
- build
- src
- tests/phpstan/DummyPluginOwned.php
- tests/phpstan/rules
- tests/phpunit
- tests/plugins/TesterPlugin
@ -46,7 +42,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

View File

@ -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.

View File

@ -1,103 +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;
/**
* Allows threads to manually trigger the cyclic garbage collector using a threshold like PHP's own garbage collector,
* but triggered at a time that suits the thread instead of in random code pathways.
*
* The GC trigger behaviour in this class was adapted from Zend/zend_gc.c as of PHP 8.3.14.
*/
final class GarbageCollectorManager{
//TODO: These values could be adjusted to better suit PM, but for now we just want to mirror PHP GC to minimize
//behavioural changes.
private const GC_THRESHOLD_TRIGGER = 100;
private const GC_THRESHOLD_MAX = 1_000_000_000;
private const GC_THRESHOLD_DEFAULT = 10_001;
private const GC_THRESHOLD_STEP = 10_000;
private int $threshold = self::GC_THRESHOLD_DEFAULT;
private int $collectionTimeTotalNs = 0;
private \Logger $logger;
private TimingsHandler $timings;
public function __construct(
\Logger $logger,
?TimingsHandler $parentTimings,
){
gc_disable();
$this->logger = new \PrefixedLogger($logger, "Cyclic Garbage Collector");
$this->timings = new TimingsHandler("Cyclic Garbage Collector", $parentTimings);
}
private function adjustGcThreshold(int $cyclesCollected, int $rootsAfterGC) : void{
//TODO Very simple heuristic for dynamic GC buffer resizing:
//If there are "too few" collections, increase the collection threshold
//by a fixed step
//Adapted from zend_gc.c/gc_adjust_threshold() as of PHP 8.3.14
if($cyclesCollected < self::GC_THRESHOLD_TRIGGER || $rootsAfterGC >= $this->threshold){
$this->threshold = min(self::GC_THRESHOLD_MAX, $this->threshold + self::GC_THRESHOLD_STEP);
}elseif($this->threshold > self::GC_THRESHOLD_DEFAULT){
$this->threshold = max(self::GC_THRESHOLD_DEFAULT, $this->threshold - self::GC_THRESHOLD_STEP);
}
}
public function getThreshold() : int{ return $this->threshold; }
public function getCollectionTimeTotalNs() : int{ return $this->collectionTimeTotalNs; }
public function maybeCollectCycles() : int{
$rootsBefore = gc_status()["roots"];
if($rootsBefore < $this->threshold){
return 0;
}
$this->timings->startTiming();
$start = hrtime(true);
$cycles = gc_collect_cycles();
$end = hrtime(true);
$rootsAfter = gc_status()["roots"];
$this->adjustGcThreshold($cycles, $rootsAfter);
$this->timings->stopTiming();
$time = $end - $start;
$this->collectionTimeTotalNs += $time;
$this->logger->debug("gc_collect_cycles: " . number_format($time) . " ns ($rootsBefore -> $rootsAfter roots, $cycles cycles collected) - total GC time: " . number_format($this->collectionTimeTotalNs) . " ns");
return $cycles;
}
}

View File

@ -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;
}
}

View File

@ -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,11 +163,8 @@ class MemoryManager{
return $this->globalMemoryLimit;
}
/**
* @deprecated
*/
public function canUseChunkCache() : bool{
return !$this->lowMemory;
return !$this->lowMemory || !$this->lowMemDisableChunkCache;
}
/**
@ -143,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)));
}
@ -195,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();
@ -205,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();
@ -227,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();
@ -239,10 +283,239 @@ class MemoryManager{
/**
* Static memory dumper accessible from any thread.
* @deprecated
* @see MemoryDump
*/
public static function dumpMemory(mixed $startingObject, string $outputFolder, int $maxNesting, int $maxStringSize, \Logger $logger) : void{
MemoryDump::dumpMemory($startingObject, $outputFolder, $maxNesting, $maxStringSize, $logger);
$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;
}
}

View File

@ -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";

View File

@ -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,6 +79,7 @@ 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;
@ -139,7 +139,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;
@ -346,6 +345,10 @@ class Server{
return $this->maxPlayers;
}
public function setMaxPlayers(int $maxPlayers) : void{
$this->maxPlayers = $maxPlayers;
}
/**
* Returns whether the server requires that players be authenticated to Xbox Live. If true, connecting players who
* are not logged into Xbox Live will be disconnected.
@ -699,7 +702,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);
@ -920,7 +923,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);
@ -1006,7 +1008,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);
@ -1016,11 +1018,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();
@ -1029,6 +1027,7 @@ 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(
@ -1181,7 +1180,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");

View File

@ -31,8 +31,8 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.25.1";
public const IS_DEVELOPMENT_BUILD = false;
public const BASE_VERSION = "5.23.2";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable";
/**

View File

@ -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';

View File

@ -70,6 +70,9 @@ class Anvil extends Transparent implements Fallable{
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->squash(Facing::axis(Facing::rotateY($this->facing, false)), 1 / 8)];
}

View File

@ -87,6 +87,9 @@ 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);
@ -113,7 +116,7 @@ class Bamboo extends Transparent{
}
public function getModelPositionOffset() : ?Vector3{
$seed = self::getOffsetSeed($this->position->getFloorX(), 0, $this->position->getFloorZ());
$seed = self::getOffsetSeed($this->position->x, 0, $this->position->z);
$retX = (($seed % 12) + 1) / 16;
$retZ = ((($seed >> 8) % 12) + 1) / 16;
return new Vector3($retX, 0, $retZ);
@ -132,7 +135,7 @@ class Bamboo extends Transparent{
private function seekToTop() : Bamboo{
$world = $this->position->getWorld();
$top = $this;
while(($next = $world->getBlock($top->position->up())) instanceof Bamboo && $next->hasSameTypeId($this)){
while(($next = $world->getBlock($top->position->getSide(Facing::UP))) instanceof Bamboo && $next->hasSameTypeId($this)){
$top = $next;
}
return $top;
@ -141,7 +144,7 @@ class Bamboo extends Transparent{
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)){
if($top->grow(self::getMaxHeight($top->position->x, $top->position->z), mt_rand(1, 2), $player)){
$item->pop();
return true;
}
@ -156,12 +159,12 @@ class Bamboo extends Transparent{
private function grow(int $maxHeight, int $growAmount, ?Player $player) : bool{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){
if(!$world->getBlock($this->position->getSide(Facing::UP))->canBeReplaced()){
return false;
}
$height = 1;
while($world->getBlock($this->position->subtract(0, $height, 0))->hasSameTypeId($this)){
while($world->getBlock($this->position->getSide(Facing::DOWN, $height))->hasSameTypeId($this)){
if(++$height >= $maxHeight){
return false;
}
@ -198,7 +201,7 @@ class Bamboo extends Transparent{
$tx = new BlockTransaction($world);
foreach($newBlocks as $idx => $newBlock){
$tx->addBlock($this->position->subtract(0, $idx - $growAmount, 0), $newBlock);
$tx->addBlock($this->position->getSide(Facing::DOWN, $idx - $growAmount), $newBlock);
}
$ev = new StructureGrowEvent($this, $tx, $player);
@ -218,10 +221,10 @@ class Bamboo extends Transparent{
$world = $this->position->getWorld();
if($this->ready){
$this->ready = false;
if($world->getFullLight($this->position) < 9 || !$this->grow(self::getMaxHeight($this->position->getFloorX(), $this->position->getFloorZ()), 1, null)){
if($world->getFullLight($this->position) < 9 || !$this->grow(self::getMaxHeight($this->position->x, $this->position->z), 1, null)){
$world->setBlock($this->position, $this);
}
}elseif($world->getBlock($this->position->up())->canBeReplaced()){
}elseif($world->getBlock($this->position->getSide(Facing::UP))->canBeReplaced()){
$this->ready = true;
$world->setBlock($this->position, $this);
}

View File

@ -73,14 +73,14 @@ final class BambooSapling extends Flowable{
private function grow(?Player $player) : bool{
$world = $this->position->getWorld();
if(!$world->getBlock($this->position->up())->canBeReplaced()){
if(!$world->getBlock($this->position->getSide(Facing::UP))->canBeReplaced()){
return false;
}
$tx = new BlockTransaction($world);
$bamboo = VanillaBlocks::BAMBOO();
$tx->addBlock($this->position, $bamboo)
->addBlock($this->position->up(), (clone $bamboo)->setLeafSize(Bamboo::SMALL_LEAVES));
->addBlock($this->position->getSide(Facing::UP), (clone $bamboo)->setLeafSize(Bamboo::SMALL_LEAVES));
$ev = new StructureGrowEvent($this, $tx, $player);
$ev->call();
@ -102,7 +102,7 @@ final class BambooSapling extends Flowable{
if($world->getFullLight($this->position) < 9 || !$this->grow(null)){
$world->setBlock($this->position, $this);
}
}elseif($world->getBlock($this->position->up())->canBeReplaced()){
}elseif($world->getBlock($this->position->getSide(Facing::UP))->canBeReplaced()){
$this->ready = true;
$world->setBlock($this->position, $this);
}

View File

@ -30,6 +30,7 @@ use pocketmine\block\utils\SupportType;
use pocketmine\item\Banner as ItemBanner;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
@ -96,6 +97,9 @@ abstract class BaseBanner extends Transparent{
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [];
}

View File

@ -99,10 +99,10 @@ abstract class BaseBigDripleaf extends Transparent{
return false;
}
$pos = $head->position;
$up = $pos->up();
$up = $pos->getSide(Facing::UP);
$world = $pos->getWorld();
if(
!$world->isInWorld($up->getFloorX(), $up->getFloorY(), $up->getFloorZ()) ||
!$world->isInWorld($up->x, $up->y, $up->z) ||
$world->getBlock($up)->getTypeId() !== BlockTypeIds::AIR
){
return false;

View File

@ -27,6 +27,7 @@ use pocketmine\block\utils\BlockEventHelper;
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{
@ -55,11 +56,9 @@ abstract class BaseCoral extends Transparent{
public function isSolid() : bool{ return false; }
protected function isCoveredWithWater() : bool{
$world = $this->position->getWorld();
$hasWater = false;
foreach($this->position->sides() as $vector3){
if($world->getBlock($vector3) instanceof Water){
foreach(Facing::ALL as $side){
if($this->getSide($side) instanceof Water){
$hasWater = true;
break;
}

View File

@ -34,6 +34,7 @@ use pocketmine\event\block\SignChangeEvent;
use pocketmine\item\Dye;
use pocketmine\item\Item;
use pocketmine\item\ItemTypeIds;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\utils\TextFormat;
@ -94,6 +95,9 @@ abstract class BaseSign extends Transparent{
return 16;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [];
}
@ -141,7 +145,7 @@ abstract class BaseSign extends Transparent{
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());
$this->position->getWorld()->addSound($this->position->center(), new InkSacUseSound());
return true;
}
return false;
@ -179,7 +183,7 @@ abstract class BaseSign extends Transparent{
$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());
$this->position->getWorld()->addSound($this->position->center(), new DyeUseSound());
return true;
}
}elseif(match($item->getTypeId()){

View File

@ -76,6 +76,9 @@ class Bed extends Transparent{
}
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 16)];
}
@ -125,7 +128,7 @@ class Bed extends Transparent{
$player->sendMessage(TextFormat::GRAY . "This bed is incomplete");
return true;
}elseif($playerPos->distanceSquared($this->position) > 4 && $playerPos->distanceSquared($other->position) > 4){
}elseif($playerPos->distanceSquared($this->position->center()) > 4 && $playerPos->distanceSquared($other->position->center()) > 4){
$player->sendMessage(KnownTranslationFactory::tile_bed_tooFar()->prefix(TextFormat::GRAY));
return true;
}

View File

@ -143,10 +143,10 @@ final class Bell extends Transparent{
public function ring(int $faceHit) : void{
$world = $this->position->getWorld();
$world->addSound($this->position, new BellRingSound());
$world->addSound($this->position->center(), new BellRingSound());
$tile = $world->getTile($this->position);
if($tile instanceof TileBell){
$world->broadcastPacketToViewers($this->position, $tile->createFakeUpdatePacket($faceHit));
$world->broadcastPacketOnBlock($this->position, $tile->createFakeUpdatePacket($faceHit));
}
}

View File

@ -93,7 +93,7 @@ class BigDripleafHead extends BaseBigDripleaf{
public function onProjectileHit(Projectile $projectile, RayTraceResult $hitResult) : void{
if($this->leafState !== DripleafState::FULL_TILT){
$this->setTiltAndScheduleTick(DripleafState::FULL_TILT);
$this->position->getWorld()->addSound($this->position, new DripleafTiltDownSound());
$this->position->getWorld()->addSound($this->position->center(), new DripleafTiltDownSound());
}
}
@ -101,13 +101,13 @@ class BigDripleafHead extends BaseBigDripleaf{
if($this->leafState !== DripleafState::STABLE){
if($this->leafState === DripleafState::FULL_TILT){
$this->position->getWorld()->setBlock($this->position, $this->setLeafState(DripleafState::STABLE));
$this->position->getWorld()->addSound($this->position, new DripleafTiltUpSound());
$this->position->getWorld()->addSound($this->position->center(), new DripleafTiltUpSound());
}else{
$this->setTiltAndScheduleTick(match($this->leafState){
DripleafState::UNSTABLE => DripleafState::PARTIAL_TILT,
DripleafState::PARTIAL_TILT => DripleafState::FULL_TILT,
});
$this->position->getWorld()->addSound($this->position, new DripleafTiltDownSound());
$this->position->getWorld()->addSound($this->position->center(), new DripleafTiltDownSound());
}
}
}

View File

@ -73,12 +73,9 @@ class Block{
protected BlockIdentifier $idInfo;
protected string $fallbackName;
protected BlockTypeInfo $typeInfo;
protected Position $position;
protected BlockPosition $position;
/**
* @var AxisAlignedBB[]|null
* @phpstan-var list<AxisAlignedBB>|null
*/
/** @var AxisAlignedBB[]|null */
protected ?array $collisionBoxes = null;
private int $requiredBlockItemStateDataBits;
@ -110,7 +107,7 @@ class Block{
$this->idInfo = $idInfo;
$this->fallbackName = $name;
$this->typeInfo = $typeInfo;
$this->position = new Position(0, 0, 0, null);
$this->position = new BlockPosition(0, 0, 0, null);
$calculator = new RuntimeDataSizeCalculator();
$this->describeBlockItemState($calculator);
@ -380,7 +377,7 @@ class Block{
*/
public function writeStateToWorld() : void{
$world = $this->position->getWorld();
$chunk = $world->getOrLoadChunkAtPosition($this->position);
$chunk = $world->loadChunk($this->position->x >> Chunk::COORD_BIT_SIZE, $this->position->z >> Chunk::COORD_BIT_SIZE);
if($chunk === null){
throw new AssumptionFailedError("World::setBlock() should have loaded the chunk before calling this method");
}
@ -401,7 +398,7 @@ class Block{
* @var Tile $tile
* @see Tile::__construct()
*/
$tile = new $tileType($world, $this->position->asVector3());
$tile = new $tileType($this->position);
$world->addTile($tile);
}
}
@ -611,7 +608,7 @@ class Block{
return false;
}
final public function getPosition() : Position{
final public function getPosition() : BlockPosition{
return $this->position;
}
@ -619,7 +616,7 @@ class Block{
* @internal
*/
final public function position(World $world, int $x, int $y, int $z) : void{
$this->position = new Position($x, $y, $z, $world);
$this->position = new BlockPosition($x, $y, $z, $world);
$this->collisionBoxes = null;
}
@ -910,15 +907,14 @@ class Block{
* - anti-cheat checks in plugins
*
* @return AxisAlignedBB[]
* @phpstan-return list<AxisAlignedBB>
*/
final public function getCollisionBoxes() : array{
if($this->collisionBoxes === null){
$this->collisionBoxes = $this->recalculateCollisionBoxes();
$extraOffset = $this->getModelPositionOffset();
$offset = $extraOffset !== null ? $this->position->addVector($extraOffset) : $this->position;
[$dx, $dy, $dz] = $extraOffset !== null ? [$extraOffset->x, $extraOffset->y, $extraOffset->z] : [0, 0, 0];
foreach($this->collisionBoxes as $bb){
$bb->offset($offset->x, $offset->y, $offset->z);
$bb->offset($this->position->x + $dx, $this->position->y + $dy, $this->position->z + $dz);
}
}
@ -935,7 +931,6 @@ class Block{
/**
* @return AxisAlignedBB[]
* @phpstan-return list<AxisAlignedBB>
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()];

View File

@ -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;

110
src/block/BlockPosition.php Normal file
View File

@ -0,0 +1,110 @@
<?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\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\world\World;
use function iterator_to_array;
final class BlockPosition implements \Stringable{
public function __construct(
public readonly int $x,
public readonly int $y,
public readonly int $z,
private ?World $world //TODO: make this non-nullable (requires Blocks not to reference positions)
){}
/**
* Returns the position's world if valid. Throws an error if the world is unexpectedly invalid.
*
* @throws AssumptionFailedError
*/
public function getWorld() : World{
if($this->world === null || !$this->world->isLoaded()){
throw new AssumptionFailedError("Position world is null or has been unloaded");
}
return $this->world;
}
/**
* Checks if this object has a valid reference to a loaded world
*/
public function isValid() : bool{
if($this->world !== null && !$this->world->isLoaded()){
$this->world = null;
return false;
}
return $this->world !== null;
}
public function asVector3() : Vector3{
return new Vector3($this->x, $this->y, $this->z);
}
public function center() : Vector3{
return new Vector3($this->x + 0.5, $this->y + 0.5, $this->z + 0.5);
}
public static function fromVector3(Vector3 $vector3, World $world) : self{
return new self($vector3->getFloorX(), $vector3->getFloorY(), $vector3->getFloorZ(), $world);
}
public function getSide(int $side, int $step = 1) : BlockPosition{
$offset = Facing::OFFSET[$side] ?? throw new \InvalidArgumentException("Invalid side $side");
[$dx, $dy, $dz] = $offset;
return new BlockPosition($this->x + ($dx * $step), $this->y + ($dy * $step), $this->z + ($dz * $step), $this->world);
}
public function add(int $x, int $y, int $z) : BlockPosition{
return new BlockPosition($this->x + $x, $this->y + $y, $this->z + $z, $this->world);
}
/**
* @phpstan-return \Generator<int, self, void, void>
*/
public function getAllSides() : \Generator{
foreach(Facing::ALL as $facing){
yield $this->getSide($facing);
}
}
/**
* @phpstan-return list<self>
*/
public function getAllSidesArray() : array{
return iterator_to_array($this->getAllSides(), preserve_keys: false);
}
public function __toString() : string{
$worldName = $this->world?->getFolderName() ?? "???";
return "BlockPosition(x=$this->x,y=$this->y,z=$this->z,world={$worldName}";
}
}

View File

@ -765,29 +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 FIRST_UNUSED_BLOCK_ID = 10759;
public const FIRST_UNUSED_BLOCK_ID = 10738;
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;

View File

@ -67,7 +67,7 @@ abstract class Button extends Flowable{
$world = $this->position->getWorld();
$world->setBlock($this->position, $this);
$world->scheduleDelayedBlockUpdate($this->position, $this->getActivationTime());
$world->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOnSound());
$world->addSound($this->position->center(), new RedstonePowerOnSound());
}
return true;
@ -78,7 +78,7 @@ abstract class Button extends Flowable{
$this->pressed = false;
$world = $this->position->getWorld();
$world->setBlock($this->position, $this);
$world->addSound($this->position->add(0.5, 0.5, 0.5), new RedstonePowerOffSound());
$world->addSound($this->position->center(), new RedstonePowerOffSound());
}
}

View File

@ -43,6 +43,9 @@ class Cactus extends Transparent{
return true;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
$shrinkSize = 1 / 16;
return [AxisAlignedBB::one()->contract($shrinkSize, 0, $shrinkSize)->trim(Facing::UP, $shrinkSize)];

View File

@ -40,6 +40,9 @@ class Cake extends BaseCake{
$w->boundedIntAuto(0, self::MAX_BITES, $this->bites);
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [
AxisAlignedBB::one()

View File

@ -36,6 +36,9 @@ class CakeWithCandle extends BaseCake{
onInteract as onInteractCandle;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [
AxisAlignedBB::one()
@ -73,6 +76,6 @@ class CakeWithCandle extends BaseCake{
public function onConsume(Living $consumer) : void{
parent::onConsume($consumer);
$this->position->getWorld()->dropItem($this->position->add(0.5, 0.5, 0.5), $this->getCandle()->asItem());
$this->position->getWorld()->dropItem($this->position->center(), $this->getCandle()->asItem());
}
}

View File

@ -185,7 +185,7 @@ class Campfire extends Transparent{
if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE){
$item->pop();
$this->ignite();
$this->position->getWorld()->addSound($this->position, new BlazeShootSound());
$this->position->getWorld()->addSound($this->position->center(), new BlazeShootSound());
return true;
}elseif($item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){
if($item instanceof Durable){
@ -205,7 +205,7 @@ class Campfire extends Transparent{
$ingredient->setCount(1);
if(count($this->inventory->addItem($ingredient)) === 0){
$item->pop();
$this->position->getWorld()->addSound($this->position, new ItemFrameAddItemSound());
$this->position->getWorld()->addSound($this->position->center(), new ItemFrameAddItemSound());
return true;
}
}
@ -259,26 +259,26 @@ class Campfire extends Transparent{
$this->inventory->setItem($slot, VanillaItems::AIR());
$this->setCookingTime($slot, 0);
$this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $ev->getResult());
$this->position->getWorld()->dropItem($this->position->asVector3()->add(0.5, 1, 0.5), $ev->getResult());
}
}
if(count($items) > 0){
$this->position->getWorld()->setBlock($this->position, $this);
}
if(mt_rand(1, 6) === 1){
$this->position->getWorld()->addSound($this->position, $furnaceType->getCookSound());
$this->position->getWorld()->addSound($this->position->center(), $furnaceType->getCookSound());
}
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS);
}
}
private function extinguish() : void{
$this->position->getWorld()->addSound($this->position, new FireExtinguishSound());
$this->position->getWorld()->addSound($this->position->center(), new FireExtinguishSound());
$this->position->getWorld()->setBlock($this->position, $this->setLit(false));
}
private function ignite() : void{
$this->position->getWorld()->addSound($this->position, new FlintSteelSound());
$this->position->getWorld()->addSound($this->position->center(), new FlintSteelSound());
$this->position->getWorld()->setBlock($this->position, $this->setLit(true));
$this->position->getWorld()->scheduleDelayedBlockUpdate($this->position, self::UPDATE_INTERVAL_TICKS);
}

View File

@ -36,6 +36,9 @@ class Carpet extends Flowable{
return true;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 15 / 16)];
}

View File

@ -69,7 +69,7 @@ final class Cauldron extends Transparent{
*/
private function fill(int $amount, FillableCauldron $result, Item $usedItem, Item $returnedItem, array &$returnedItems) : void{
$this->position->getWorld()->setBlock($this->position, $result->setFillLevel($amount));
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), $result->getFillSound());
$this->position->getWorld()->addSound($this->position->center(), $result->getFillSound());
$usedItem->pop();
$returnedItems[] = $returnedItem;
@ -95,10 +95,10 @@ final class Cauldron extends Transparent{
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
if($world->getBlock($this->position->up())->getTypeId() === BlockTypeIds::WATER){
if($this->getSide(Facing::UP)->getTypeId() === BlockTypeIds::WATER){
$cauldron = VanillaBlocks::WATER_CAULDRON()->setFillLevel(FillableCauldron::MAX_FILL_LEVEL);
$world->setBlock($this->position, $cauldron);
$world->addSound($this->position->add(0.5, 0.5, 0.5), $cauldron->getFillSound());
$world->addSound($this->position->center(), $cauldron->getFillSound());
}
}
}

View File

@ -90,8 +90,8 @@ class CaveVines extends Flowable{
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());
$this->position->getWorld()->dropItem($this->position->center(), $this->asItem());
$this->position->getWorld()->addSound($this->position->center(), new GlowBerriesPickSound());
$this->position->getWorld()->setBlock($this->position, $this->setBerries(false));
return true;
@ -117,7 +117,7 @@ class CaveVines extends Flowable{
if($this->age < self::MAX_AGE && mt_rand(1, 10) === 1){
$growthPos = $this->position->getSide(Facing::DOWN);
$world = $growthPos->getWorld();
if($world->isInWorld($growthPos->getFloorX(), $growthPos->getFloorY(), $growthPos->getFloorZ())){
if($world->isInWorld($growthPos->x, $growthPos->y, $growthPos->z)){
$block = $world->getBlock($growthPos);
if($block->getTypeId() === BlockTypeIds::AIR){
$newState = VanillaBlocks::CAVE_VINES()

View File

@ -36,6 +36,9 @@ use pocketmine\player\Player;
class Chest extends Transparent{
use FacesOppositePlacingPlayerTrait;
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
//these are slightly bigger than in PC
return [AxisAlignedBB::one()->contract(0.025, 0, 0.025)->trim(Facing::UP, 0.05)];

View File

@ -27,15 +27,12 @@ use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\entity\projectile\Projectile;
use pocketmine\event\block\StructureGrowEvent;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\RayTraceResult;
use pocketmine\math\Vector3;
use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\ChorusFlowerDieSound;
use pocketmine\world\sound\ChorusFlowerGrowSound;
use pocketmine\world\World;
use function array_rand;
use function min;
use function mt_rand;
@ -54,24 +51,22 @@ final class ChorusFlower extends Flowable{
}
private function canBeSupportedAt(Block $block) : bool{
$position = $block->position;
$world = $position->getWorld();
$down = $world->getBlock($position->down());
$down = $block->getSide(Facing::DOWN);
if($down->getTypeId() === BlockTypeIds::END_STONE || $down->getTypeId() === BlockTypeIds::CHORUS_PLANT){
return true;
}
$plantAdjacent = false;
foreach($position->sidesAroundAxis(Axis::Y) as $sidePosition){
$block = $world->getBlock($sidePosition);
foreach(Facing::HORIZONTAL as $side){
$sideBlock = $block->getSide($side);
if($block->getTypeId() === BlockTypeIds::CHORUS_PLANT){
if($sideBlock->getTypeId() === BlockTypeIds::CHORUS_PLANT){
if($plantAdjacent){ //at most one plant may be horizontally adjacent
return false;
}
$plantAdjacent = true;
}elseif($block->getTypeId() !== BlockTypeIds::AIR){
}elseif($sideBlock->getTypeId() !== BlockTypeIds::AIR){
return false;
}
}
@ -92,7 +87,7 @@ final class ChorusFlower extends Flowable{
$stemHeight = 0;
$endStoneBelow = false;
for($yOffset = 0; $yOffset < self::MAX_STEM_HEIGHT; $yOffset++, $stemHeight++){
$down = $world->getBlock($this->position->down($yOffset + 1));
$down = $this->getSide(Facing::DOWN, $yOffset + 1);
if($down->getTypeId() !== BlockTypeIds::CHORUS_PLANT){
if($down->getTypeId() === BlockTypeIds::END_STONE){
@ -105,12 +100,13 @@ final class ChorusFlower extends Flowable{
return [$stemHeight, $endStoneBelow];
}
private function allHorizontalBlocksEmpty(World $world, Vector3 $position, ?int $except) : bool{
foreach($position->sidesAroundAxis(Axis::Y) as $facing => $sidePosition){
private function allHorizontalBlocksEmpty(BlockPosition $position, ?int $except) : bool{
$world = $position->getWorld();
foreach(Facing::HORIZONTAL as $facing){
if($facing === $except){
continue;
}
if($world->getBlock($sidePosition)->getTypeId() !== BlockTypeIds::AIR){
if($world->getBlock($position->getSide($facing))->getTypeId() !== BlockTypeIds::AIR){
return false;
}
}
@ -121,7 +117,7 @@ final class ChorusFlower extends Flowable{
private function canGrowUpwards(int $stemHeight, bool $endStoneBelow) : bool{
$world = $this->position->getWorld();
$up = $this->position->up();
$up = $this->position->getSide(Facing::UP);
if(
//the space above must be empty and writable
!$world->isInWorld($up->x, $up->y, $up->z) ||
@ -129,7 +125,7 @@ final class ChorusFlower extends Flowable{
(
//the space above that must be empty, but doesn't need to be writable
$world->isInWorld($up->x, $up->y + 1, $up->z) &&
$world->getBlock($up->up())->getTypeId() !== BlockTypeIds::AIR
$world->getBlock($up->getSide(Facing::UP))->getTypeId() !== BlockTypeIds::AIR
)
){
return false;
@ -145,7 +141,7 @@ final class ChorusFlower extends Flowable{
}
}
return $this->allHorizontalBlocksEmpty($world, $up, null);
return $this->allHorizontalBlocksEmpty($up, null);
}
private function grow(int $facing, int $ageChange, ?BlockTransaction $tx) : BlockTransaction{
@ -183,8 +179,8 @@ final class ChorusFlower extends Flowable{
$sidePosition = $this->position->getSide($facing);
if(
$world->getBlock($sidePosition)->getTypeId() === BlockTypeIds::AIR &&
$world->getBlock($sidePosition->down())->getTypeId() === BlockTypeIds::AIR &&
$this->allHorizontalBlocksEmpty($world, $sidePosition, Facing::opposite($facing))
$world->getBlock($sidePosition->getSide(Facing::DOWN))->getTypeId() === BlockTypeIds::AIR &&
$this->allHorizontalBlocksEmpty($sidePosition, Facing::opposite($facing))
){
$tx = $this->grow($facing, 1, $tx);
}
@ -196,10 +192,10 @@ final class ChorusFlower extends Flowable{
$ev = new StructureGrowEvent($this, $tx, null);
$ev->call();
if(!$ev->isCancelled() && $tx->apply()){
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ChorusFlowerGrowSound());
$world->addSound($this->position->center(), new ChorusFlowerGrowSound());
}
}else{
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ChorusFlowerDieSound());
$world->addSound($this->position->center(), new ChorusFlowerDieSound());
$this->position->getWorld()->setBlock($this->position, $this->setAge(self::MAX_AGE));
}
}

View File

@ -26,7 +26,6 @@ namespace pocketmine\block;
use pocketmine\block\utils\StaticSupportTrait;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use function mt_rand;
@ -51,21 +50,18 @@ final class ChorusPlant extends Flowable{
}
private function canBeSupportedAt(Block $block) : bool{
$position = $block->position;
$world = $position->getWorld();
$down = $block->getSide(Facing::DOWN);
$verticalAir = $down->getTypeId() === BlockTypeIds::AIR || $block->getSide(Facing::UP)->getTypeId() === BlockTypeIds::AIR;
$down = $world->getBlock($position->down());
$verticalAir = $down->getTypeId() === BlockTypeIds::AIR || $world->getBlock($position->up())->getTypeId() === BlockTypeIds::AIR;
foreach(Facing::HORIZONTAL as $facing){
$sideBlock = $block->getSide($facing);
foreach($position->sidesAroundAxis(Axis::Y) as $sidePosition){
$block = $world->getBlock($sidePosition);
if($block->getTypeId() === BlockTypeIds::CHORUS_PLANT){
if($sideBlock->getTypeId() === BlockTypeIds::CHORUS_PLANT){
if(!$verticalAir){
return false;
}
if($this->canBeSupportedBy($block->getSide(Facing::DOWN))){
if($this->canBeSupportedBy($sideBlock->getSide(Facing::DOWN))){
return true;
}
}

View File

@ -50,6 +50,9 @@ class CocoaBlock extends Flowable{
$w->boundedIntAuto(0, self::MAX_AGE, $this->age);
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [
AxisAlignedBB::one()

View File

@ -26,6 +26,7 @@ namespace pocketmine\block;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\CoralTypeTrait;
use pocketmine\item\Item;
use pocketmine\math\Facing;
use function mt_rand;
final class CoralBlock extends Opaque{
@ -39,11 +40,9 @@ final class CoralBlock extends Opaque{
public function onScheduledUpdate() : void{
if(!$this->dead){
$world = $this->position->getWorld();
$hasWater = false;
foreach($this->position->sides() as $vector3){
if($world->getBlock($vector3) instanceof Water){
foreach(Facing::ALL as $facing){
if($this->getSide($facing) instanceof Water){
$hasWater = true;
break;
}

View File

@ -62,6 +62,9 @@ class DaylightSensor extends Transparent{
return 300;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 10 / 16)];
}

View File

@ -63,7 +63,7 @@ class Dirt extends Opaque{
$item->applyDamage(1);
$newBlock = $this->dirtType === DirtType::NORMAL ? VanillaBlocks::FARMLAND() : VanillaBlocks::DIRT();
$center = $this->position->add(0.5, 0.5, 0.5);
$center = $this->position->center();
$world->addSound($center, new ItemUseOnBlockSound($newBlock));
$world->setBlock($this->position, $newBlock);
if($this->dirtType === DirtType::ROOTED){
@ -83,7 +83,7 @@ class Dirt extends Opaque{
}elseif(($item instanceof Potion || $item instanceof SplashPotion) && $item->getType() === PotionType::WATER){
$item->pop();
$world->setBlock($this->position, VanillaBlocks::MUD());
$world->addSound($this->position, new WaterSplashSound(0.5));
$world->addSound($this->position->center(), new WaterSplashSound(0.5));
return true;
}

View File

@ -95,6 +95,9 @@ class Door extends Transparent{
return false;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
//TODO: doors are 0.1825 blocks thick, instead of 0.1875 like JE (https://bugs.mojang.com/browse/MCPE-19214)
return [AxisAlignedBB::one()->trim($this->open ? Facing::rotateY($this->facing, !$this->hingeRight) : $this->facing, 327 / 400)];
@ -149,7 +152,7 @@ class Door extends Transparent{
}
$world->setBlock($this->position, $this);
$world->addSound($this->position, new DoorSound());
$world->addSound($this->position->center(), new DoorSound());
return true;
}

View File

@ -66,16 +66,17 @@ class DragonEgg extends Transparent implements Fallable{
$this->position->z + mt_rand(-16, 16)
);
if($block instanceof Air){
$ev = new BlockTeleportEvent($this, $block->position);
//TODO: this needs migrating to BlockPosition, but having World in there presents some issues
$ev = new BlockTeleportEvent($this, $block->position->asVector3());
$ev->call();
if($ev->isCancelled()){
break;
}
$blockPos = $ev->getTo();
$world->addParticle($this->position, new DragonEggTeleportParticle($this->position->x - $blockPos->x, $this->position->y - $blockPos->y, $this->position->z - $blockPos->z));
$world->addParticle($this->position->center(), new DragonEggTeleportParticle($this->position->x - $blockPos->getFloorX(), $this->position->y - $blockPos->getFloorY(), $this->position->z - $blockPos->getFloorZ()));
$world->setBlock($this->position, VanillaBlocks::AIR());
$world->setBlock($blockPos, $this);
$world->setBlockAt($blockPos->getFloorX(), $blockPos->getFloorY(), $blockPos->getFloorZ(), $this);
break;
}
}

View File

@ -33,6 +33,9 @@ use pocketmine\player\Player;
class EnchantingTable extends Transparent{
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 0.25)];
}

View File

@ -50,6 +50,9 @@ class EndPortalFrame extends Opaque{
return 1;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 3 / 16)];
}

View File

@ -52,6 +52,9 @@ class EndRod extends Flowable{
return 14;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
$myAxis = Facing::axis($this->facing);

View File

@ -40,6 +40,9 @@ class EnderChest extends Transparent{
return 7;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
//these are slightly bigger than in PC
return [AxisAlignedBB::one()->contract(0.025, 0, 0.025)->trim(Facing::UP, 0.05)];

View File

@ -94,6 +94,9 @@ class Farmland extends Transparent{
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)];
}
@ -158,9 +161,9 @@ class Farmland extends Transparent{
protected function canHydrate() : bool{
$world = $this->position->getWorld();
$startX = $this->position->getFloorX() - (int) (self::WATER_SEARCH_HORIZONTAL_LENGTH / 2);
$startY = $this->position->getFloorY();
$startZ = $this->position->getFloorZ() - (int) (self::WATER_SEARCH_HORIZONTAL_LENGTH / 2);
$startX = $this->position->x - (int) (self::WATER_SEARCH_HORIZONTAL_LENGTH / 2);
$startY = $this->position->y;
$startZ = $this->position->z - (int) (self::WATER_SEARCH_HORIZONTAL_LENGTH / 2);
if($this->waterPositionIndex !== self::WATER_POSITION_INDEX_UNKNOWN){
$raw = $this->waterPositionIndex;

View File

@ -54,9 +54,13 @@ class Fence extends Transparent{
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
$inset = 0.5 - $this->getThickness() / 2;
/** @var AxisAlignedBB[] $bbs */
$bbs = [];
$connectWest = isset($this->connections[Facing::WEST]);

View File

@ -64,6 +64,9 @@ class FenceGate extends Transparent{
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return $this->open ? [] : [AxisAlignedBB::one()->extend(Facing::UP, 0.5)->squash(Facing::axis($this->facing), 6 / 16)];
}
@ -108,7 +111,7 @@ class FenceGate extends Transparent{
$world = $this->position->getWorld();
$world->setBlock($this->position, $this);
$world->addSound($this->position, new DoorSound());
$world->addSound($this->position->center(), new DoorSound());
return true;
}

View File

@ -79,7 +79,7 @@ abstract class FillableCauldron extends Transparent{
return;
}
$this->position->getWorld()->setBlock($this->position, $this->withFillLevel($this->fillLevel + $amount));
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), $this->getFillSound());
$this->position->getWorld()->addSound($this->position->center(), $this->getFillSound());
$usedItem->pop();
$returnedItems[] = $returnedItem;
@ -94,7 +94,7 @@ abstract class FillableCauldron extends Transparent{
}
$this->position->getWorld()->setBlock($this->position, $this->withFillLevel($this->fillLevel - $amount));
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), $this->getEmptySound());
$this->position->getWorld()->addSound($this->position->center(), $this->getEmptySound());
$usedItem->pop();
$returnedItems[] = $returnedItem;

View File

@ -161,7 +161,7 @@ class Fire extends BaseFire{
$ageDivisor = $this->age + 30;
for($y = -1; $y <= 4; ++$y){
$targetY = $y + (int) $this->position->y;
$targetY = $y + $this->position->y;
if($targetY < World::Y_MIN || $targetY >= World::Y_MAX){
continue;
}
@ -169,12 +169,12 @@ class Fire extends BaseFire{
$randomBound = 100 + ($y > 1 ? ($y - 1) * 100 : 0);
for($z = -1; $z <= 1; ++$z){
$targetZ = $z + (int) $this->position->z;
$targetZ = $z + $this->position->z;
for($x = -1; $x <= 1; ++$x){
if($x === 0 && $y === 0 && $z === 0){
continue;
}
$targetX = $x + (int) $this->position->x;
$targetX = $x + $this->position->x;
if(!$world->isInWorld($targetX, $targetY, $targetZ)){
continue;
}
@ -190,9 +190,10 @@ class Fire extends BaseFire{
//TODO: fire can't spread if it's raining in any horizontally adjacent block, or the current one
$encouragement = 0;
foreach($block->position->sides() as $vector3){
if($world->isInWorld($vector3->x, $vector3->y, $vector3->z)){
$encouragement = max($encouragement, $world->getBlockAt($vector3->x, $vector3->y, $vector3->z)->getFlameEncouragement());
foreach(Facing::ALL as $facing){
$sidePos = $block->position->getSide($facing);
if($world->isInWorld($sidePos->x, $sidePos->y, $sidePos->z)){
$encouragement = max($encouragement, $world->getBlockAt($sidePos->x, $sidePos->y, $sidePos->z)->getFlameEncouragement());
}
}

View File

@ -58,7 +58,7 @@ final class FloorCoralFan extends BaseCoral{
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
if($player !== null){
$playerBlockPos = $player->getPosition()->floor();
$directionVector = $blockReplace->position->subtractVector($playerBlockPos)->normalize();
$directionVector = $blockReplace->position->asVector3()->subtractVector($playerBlockPos)->normalize();
$angle = rad2deg(atan2($directionVector->getZ(), $directionVector->getX()));
if($angle <= 45 || 315 <= $angle || (135 <= $angle && $angle <= 225)){

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\SupportType;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
/**
@ -45,6 +46,9 @@ abstract class Flowable extends Transparent{
parent::canBePlacedAt($blockReplace, $clickVector, $face, $isClickedBlock);
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [];
}

View File

@ -83,6 +83,9 @@ class FlowerPot extends Flowable{
return $block->hasTypeTag(BlockTypeTags::POTTABLE_PLANTS);
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->contract(3 / 16, 0, 3 / 16)->trim(Facing::UP, 5 / 8)];
}
@ -108,7 +111,7 @@ class FlowerPot extends Flowable{
$removedItems = $player->getInventory()->addItem(...$removedItems);
}
foreach($removedItems as $drops){
$world->dropItem($this->position->add(0.5, 0.5, 0.5), $drops);
$world->dropItem($this->position->center(), $drops);
}
$this->setPlant(null);

View File

@ -73,7 +73,7 @@ class Furnace extends Opaque{
$furnace = $world->getTile($this->position);
if($furnace instanceof TileFurnace && $furnace->onUpdate()){
if(mt_rand(1, 60) === 1){ //in vanilla this is between 1 and 5 seconds; try to average about 3
$world->addSound($this->position, $furnace->getFurnaceType()->getCookSound());
$world->addSound($this->position->center(), $furnace->getFurnaceType()->getCookSound());
}
$world->scheduleDelayedBlockUpdate($this->position, 1); //TODO: check this
}

View File

@ -28,10 +28,10 @@ use pocketmine\block\utils\MultiAnySupportTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\World;
use function count;
use function shuffle;
@ -46,6 +46,9 @@ class GlowLichen extends Transparent{
return false;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [];
}
@ -80,7 +83,8 @@ class GlowLichen extends Transparent{
return $result->setFace($spreadFace, true);
}
private function spread(World $world, Vector3 $replacePos, int $spreadFace) : bool{
private function spread(BlockPosition $replacePos, int $spreadFace) : bool{
$world = $replacePos->getWorld();
$supportBlock = $world->getBlock($replacePos->getSide($spreadFace));
$supportFace = Facing::opposite($spreadFace);
@ -113,12 +117,10 @@ class GlowLichen extends Transparent{
}
private function spreadAroundSupport(int $sourceFace) : bool{
$world = $this->position->getWorld();
$supportPos = $this->position->getSide($sourceFace);
foreach(self::getShuffledSpreadFaces($sourceFace) as $spreadFace){
$replacePos = $supportPos->getSide($spreadFace);
if($this->spread($world, $replacePos, Facing::opposite($spreadFace))){
if($this->spread($replacePos, Facing::opposite($spreadFace))){
return true;
}
}
@ -127,11 +129,9 @@ class GlowLichen extends Transparent{
}
private function spreadAdjacentToSupport(int $sourceFace) : bool{
$world = $this->position->getWorld();
foreach(self::getShuffledSpreadFaces($sourceFace) as $spreadFace){
$replacePos = $this->position->getSide($spreadFace);
if($this->spread($world, $replacePos, $sourceFace)){
if($this->spread($replacePos, $sourceFace)){
return true;
}
}
@ -140,7 +140,7 @@ class GlowLichen extends Transparent{
private function spreadWithinSelf(int $sourceFace) : bool{
foreach(self::getShuffledSpreadFaces($sourceFace) as $spreadFace){
if(!$this->hasFace($spreadFace) && $this->spread($this->position->getWorld(), $this->position, $spreadFace)){
if(!$this->hasFace($spreadFace) && $this->spread($this->position, $spreadFace)){
return true;
}
}

View File

@ -88,7 +88,7 @@ class Grass extends Opaque{
$world = $this->position->getWorld();
if($item instanceof Fertilizer){
$item->pop();
TallGrassObject::growGrass($world, $this->position, new Random(mt_rand()), 8, 2);
TallGrassObject::growGrass($world, $this->position->center(), new Random(mt_rand()), 8, 2);
return true;
}
@ -96,14 +96,14 @@ class Grass extends Opaque{
if($item instanceof Hoe){
$item->applyDamage(1);
$newBlock = VanillaBlocks::FARMLAND();
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
$world->addSound($this->position->center(), new ItemUseOnBlockSound($newBlock));
$world->setBlock($this->position, $newBlock);
return true;
}elseif($item instanceof Shovel){
$item->applyDamage(1);
$newBlock = VanillaBlocks::GRASS_PATH();
$world->addSound($this->position->add(0.5, 0.5, 0.5), new ItemUseOnBlockSound($newBlock));
$world->addSound($this->position->center(), new ItemUseOnBlockSound($newBlock));
$world->setBlock($this->position, $newBlock);
return true;

View File

@ -29,6 +29,9 @@ use pocketmine\math\Facing;
class GrassPath extends Transparent{
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)];
}

View File

@ -135,11 +135,11 @@ class ItemFrame extends Flowable{
if($this->framedItem !== null){
$this->itemRotation = ($this->itemRotation + 1) % self::ROTATIONS;
$this->position->getWorld()->addSound($this->position, new ItemFrameRotateItemSound());
$this->position->getWorld()->addSound($this->position->center(), new ItemFrameRotateItemSound());
}elseif(!$item->isNull()){
$this->framedItem = $item->pop();
$this->position->getWorld()->addSound($this->position, new ItemFrameAddItemSound());
$this->position->getWorld()->addSound($this->position->center(), new ItemFrameAddItemSound());
}else{
return true;
}
@ -155,8 +155,8 @@ class ItemFrame extends Flowable{
}
$world = $this->position->getWorld();
if(Utils::getRandomFloat() <= $this->itemDropChance){
$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
$world->addSound($this->position, new ItemFrameRemoveItemSound());
$world->dropItem($this->position->center(), clone $this->framedItem);
$world->addSound($this->position->center(), new ItemFrameRemoveItemSound());
}
$this->setFramedItem(null);
$world->setBlock($this->position, $this);

View File

@ -61,7 +61,7 @@ class Jukebox extends Opaque{
public function ejectRecord() : void{
if($this->record !== null){
$this->position->getWorld()->dropItem($this->position->add(0.5, 1, 0.5), $this->record);
$this->position->getWorld()->dropItem($this->position->asVector3()->add(0.5, 1, 0.5), $this->record);
$this->record = null;
$this->stopSound();
}
@ -76,12 +76,12 @@ class Jukebox extends Opaque{
public function startSound() : void{
if($this->record !== null){
$this->position->getWorld()->addSound($this->position, new RecordSound($this->record->getRecordType()));
$this->position->getWorld()->addSound($this->position->center(), new RecordSound($this->record->getRecordType()));
}
}
public function stopSound() : void{
$this->position->getWorld()->addSound($this->position, new RecordStopSound());
$this->position->getWorld()->addSound($this->position->center(), new RecordStopSound());
}
public function onBreak(Item $item, ?Player $player = null, array &$returnedItems = []) : bool{

View File

@ -51,13 +51,16 @@ class Ladder extends Transparent{
}
public function onEntityInside(Entity $entity) : bool{
if($entity instanceof Living && $entity->getPosition()->floor()->distanceSquared($this->position) < 1){ //entity coordinates must be inside block
if($entity instanceof Living && $entity->getPosition()->floor()->distanceSquared($this->position->asVector3()) < 1){ //entity coordinates must be inside block
$entity->resetFallDistance();
$entity->onGround = true;
}
return true;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim($this->facing, 13 / 16)];
}

View File

@ -59,6 +59,9 @@ class Lantern extends Transparent{
return $this->lightLevel;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [
AxisAlignedBB::one()

View File

@ -81,7 +81,7 @@ class Leaves extends Transparent{
* @phpstan-param array<int, true> $visited
* @phpstan-param-out array<int, true> $visited
*/
protected function findLog(Vector3 $pos, array &$visited = [], int $distance = 0) : bool{
protected function findLog(BlockPosition $pos, array &$visited = [], int $distance = 0) : bool{
$index = World::blockHash($pos->x, $pos->y, $pos->z);
if(isset($visited[$index])){
return false;
@ -157,7 +157,6 @@ class Leaves extends Transparent{
LeavesType::MANGROVE, //TODO: mangrove propagule
LeavesType::AZALEA, LeavesType::FLOWERING_AZALEA => null, //TODO: azalea
LeavesType::CHERRY => null, //TODO: cherry
LeavesType::PALE_OAK => null, //TODO: pale oak
})?->asItem();
if($sapling !== null){
$drops[] = $sapling;

View File

@ -123,7 +123,7 @@ class Lectern extends Transparent{
if($this->book === null && $item instanceof WritableBookBase){
$world = $this->position->getWorld();
$world->setBlock($this->position, $this->setBook($item));
$world->addSound($this->position, new LecternPlaceBookSound());
$world->addSound($this->position->center(), new LecternPlaceBookSound());
$item->pop();
}
return true;
@ -132,7 +132,7 @@ class Lectern extends Transparent{
public function onAttack(Item $item, int $face, ?Player $player = null) : bool{
if($this->book !== null){
$world = $this->position->getWorld();
$world->dropItem($this->position->up(), $this->book);
$world->dropItem($this->position->getSide(Facing::UP)->center(), $this->book);
$world->setBlock($this->position, $this->setBook(null));
}
return false;

View File

@ -95,7 +95,7 @@ class Lever extends Flowable{
$world = $this->position->getWorld();
$world->setBlock($this->position, $this);
$world->addSound(
$this->position->add(0.5, 0.5, 0.5),
$this->position->center(),
$this->activated ? new RedstonePowerOnSound() : new RedstonePowerOffSound()
);
return true;

View File

@ -30,6 +30,7 @@ use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\utils\Utils;
@ -88,6 +89,9 @@ abstract class Liquid extends Transparent{
return false;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [];
}
@ -161,9 +165,9 @@ abstract class Liquid extends Transparent{
$vX = $vY = $vZ = 0;
$x = $this->position->getFloorX();
$y = $this->position->getFloorY();
$z = $this->position->getFloorZ();
$x = $this->position->x;
$y = $this->position->y;
$z = $this->position->z;
$decay = $this->getEffectiveFlowDecay($this);
@ -255,9 +259,9 @@ abstract class Liquid extends Transparent{
$world = $this->position->getWorld();
$x = $this->position->getFloorX();
$y = $this->position->getFloorY();
$z = $this->position->getFloorZ();
$x = $this->position->x;
$y = $this->position->y;
$z = $this->position->z;
if(!$this->isSource()){
$smallestFlowDecay = -100;
@ -364,7 +368,7 @@ abstract class Liquid extends Transparent{
protected function liquidCollide(Block $cause, Block $result) : bool{
if(BlockEventHelper::form($this, $result, $cause)){
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8));
$this->position->getWorld()->addSound($this->position->center(), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8));
}
return true;
}

View File

@ -104,6 +104,9 @@ class MobHead extends Flowable{
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
$collisionBox = AxisAlignedBB::one()
->contract(0.25, 0, 0.25)

View File

@ -28,6 +28,7 @@ use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\Entity;
use pocketmine\item\Item;
use pocketmine\math\Axis;
use pocketmine\math\AxisAlignedBB;
class NetherPortal extends Transparent{
@ -61,6 +62,9 @@ class NetherPortal extends Transparent{
return false;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [];
}

View File

@ -117,7 +117,7 @@ class NetherVines extends Flowable{
for($i = 1; $i <= $growthAmount; $i++){
$growthPos = $pos->getSide($this->growthFace, $i);
if(!$world->isInWorld($growthPos->getFloorX(), $growthPos->getFloorY(), $growthPos->getFloorZ()) || !$world->getBlock($growthPos)->canBeReplaced()){
if(!$world->isInWorld($growthPos->x, $growthPos->y, $growthPos->z) || !$world->getBlock($growthPos)->canBeReplaced()){
break;
}
$tx->addBlock($growthPos, (clone $top)->setAge(min(++$age, self::MAX_AGE)));

View File

@ -89,7 +89,7 @@ class PinkPetals extends Flowable{
if($this->count < self::MAX_COUNT){
$grew = BlockEventHelper::grow($this, (clone $this)->setCount($this->count + 1), $player);
}else{
$this->position->getWorld()->dropItem($this->position->add(0, 0.5, 0), $this->asItem());
$this->position->getWorld()->dropItem($this->position->center(), $this->asItem());
$grew = true;
}
if($grew){

View File

@ -73,7 +73,7 @@ final class PitcherCrop extends Flowable{
$tx = new BlockTransaction($this->position->getWorld());
$tx->addBlock($this->position, VanillaBlocks::DOUBLE_PITCHER_CROP()->setTop(false));
$tx->addBlock($this->position->up(), VanillaBlocks::DOUBLE_PITCHER_CROP()->setTop(true));
$tx->addBlock($this->position->getSide(Facing::UP), VanillaBlocks::DOUBLE_PITCHER_CROP()->setTop(true));
$ev = new StructureGrowEvent($this, $tx, $player);
$ev->call();

View File

@ -27,6 +27,7 @@ use pocketmine\block\tile\Cauldron as TileCauldron;
use pocketmine\item\Item;
use pocketmine\item\ItemTypeIds;
use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\sound\CauldronEmptyPotionSound;
@ -107,10 +108,10 @@ final class PotionCauldron extends FillableCauldron{
public function onNearbyBlockChange() : void{
$world = $this->position->getWorld();
if($world->getBlock($this->position->up())->getTypeId() === BlockTypeIds::WATER){
if($this->getSide(Facing::UP)->getTypeId() === BlockTypeIds::WATER){
$cauldron = VanillaBlocks::WATER_CAULDRON()->setFillLevel(FillableCauldron::MAX_FILL_LEVEL);
$world->setBlock($this->position, $cauldron);
$world->addSound($this->position->add(0.5, 0.5, 0.5), $cauldron->getFillSound());
$world->addSound($this->position->center(), $cauldron->getFillSound());
}
}
}

View File

@ -139,7 +139,7 @@ abstract class PressurePlate extends Transparent{
if($newState !== null){
$world->setBlock($this->position, $newState);
if($pressedChange !== null){
$world->addSound($this->position, $pressedChange ?
$world->addSound($this->position->center(), $pressedChange ?
new PressurePlateActivateSound($this) :
new PressurePlateDeactivateSound($this)
);

View File

@ -38,7 +38,7 @@ class Pumpkin extends Opaque{
$item->applyDamage(1);
$world = $this->position->getWorld();
$world->setBlock($this->position, VanillaBlocks::CARVED_PUMPKIN()->setFacing($face));
$world->dropItem($this->position->add(0.5, 0.5, 0.5), VanillaItems::PUMPKIN_SEEDS()->setCount(1));
$world->dropItem($this->position->center(), VanillaItems::PUMPKIN_SEEDS()->setCount(1));
return true;
}
return false;

View File

@ -79,6 +79,9 @@ class RedstoneComparator extends Flowable{
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 8)];
}

View File

@ -62,6 +62,9 @@ class RedstoneRepeater extends Flowable{
return $this;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [AxisAlignedBB::one()->trim(Facing::UP, 7 / 8)];
}

View File

@ -1,54 +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\MultiAnySupportTrait;
use pocketmine\block\utils\SupportType;
final class ResinClump extends Transparent{
use MultiAnySupportTrait;
public function isSolid() : bool{
return false;
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE;
}
public function canBeReplaced() : bool{
return true;
}
/**
* @return int[]
*/
protected function getInitialPlaceFaces(Block $blockReplace) : array{
return $blockReplace instanceof ResinClump ? $blockReplace->faces : [];
}
protected function recalculateCollisionBoxes() : array{
return [];
}
}

Some files were not shown because too many files have changed in this diff Show More