Compare commits

..

89 Commits

Author SHA1 Message Date
abb004fbc5 Ready 5.28.1 (#6696) 2025-05-17 15:00:53 +01:00
e0864e7ee8 composer: also axe unnecessary ctype polyfill 2025-05-17 14:54:26 +01:00
dca37d5842 Hack: forcibly remove symfony/polyfill-mbstring
we don't need this dependency anyway because mbstring is already provided.
2025-05-17 14:11:57 +01:00
67f3bb9c52 Update composer dependencies
and fix an error found by new PHPStan update
2025-05-17 13:46:33 +01:00
04de72e85e Fix changelog typo (#6690) 2025-05-10 14:34:37 +01:00
d90fc3415c fixed wrong version info (#6689) 2025-05-09 16:33:55 +01:00
134c7309c5 5.28.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/14931216524
2025-05-09 14:30:04 +00:00
5e830c7320 Protocol changes for 1.21.80 (#6687)
* Bedrock 1.21.80 support

* Update bedrock-data

* Add required tags to models

* Fixed biome data loading

* Support newest world format
apparently I messed up the blockstate data version last time around... it hasn't changed since 1.21.60

* always CS has to complain...

* Sync with release versions

* Ready 5.28.0 release

* this might help...

---------

Co-authored-by: Dylan T. <dktapps@pmmp.io>
2025-05-09 15:29:05 +01:00
d789c75c00 Improve PHPStan error reporting for unsafe foreaches
these are actually two separate concerns: one for dodgy PHPStan type suppression on implicit keys, and the other for arrays being casted to strings by PHP.
2025-05-08 02:26:09 +01:00
f2e7473629 Update PHP-CS-Fixer 2025-05-04 17:19:15 +01:00
6f3506360e Bump the github-actions group with 3 updates (#6683) 2025-05-01 08:30:26 +00:00
1ea5c060fd bruh 2025-04-20 18:16:54 +01:00
4a5c1e7540 Entity: truncate fire ticks instead of throwing exceptions
as written in the comments, it's not reasonable to propagate this limitation, since it
ultimately comes from a shortfall in the Mojang save format, not a limitation of PM's
capability. It's also not obvious how this would be propagated to the likes of setOnFire(),
as this would translate into a max time of 1638 seconds, a value no one is going to
remember.

There's a case to be made for truncating this on save rather than on initial set, but
this is at least better than having Fire Aspect level 1000 cause crashes and whatever
other gameplay logic that would have to work around this stupid limitation.
2025-04-20 16:57:44 +01:00
2548422973 AvailableEnchantmentRegistry: reject non-string tags
fixes https://crash.pmmp.io/view/12627328
2025-04-20 16:44:23 +01:00
f661443ec7 Update Ubuntu base image for GitHub Actions 2025-04-15 16:48:13 +01:00
835c383d4e Update Composer dependencies 2025-04-06 20:00:21 +01:00
d3f6c22996 5.27.2 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/14288755593
2025-04-06 03:53:08 +00:00
6f3851be80 5.27.1 (#6670) 2025-04-06 04:52:15 +01:00
e88b81a4cb 5.27.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/14110940403
2025-03-27 15:49:33 +00:00
687112f4cd 5.27.0, Bedrock 1.21.70 support (#6665)
Co-authored-by: Dylan K. Taylor <dktapps@pmmp.io>
2025-03-27 15:48:19 +00:00
c9e85603b0 Bump phpstan/phpstan-strict-rules in the development-patch-updates group (#6664) 2025-03-24 14:43:29 +00:00
c80a4d5b55 5.26.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/14011298411
2025-03-22 18:33:19 +00:00
f416cb8902 Merge pull request #6658 from pmmp/minor-next-release 2025-03-22 18:32:15 +00:00
f123df5e0d changelog: use more accurate terminology 2025-03-22 18:16:33 +00:00
de26ebd124 Prepare 5.26.0 release 2025-03-22 17:59:20 +00:00
1c6a4bde86 Bump pocketmine/locale-data in the production-patch-updates group (#6656) 2025-03-17 13:20:59 +00:00
c2f8e9365b BlockStateToObjectDeserializer: check that the returned state is actually registered
if not, this will cause random crashes in core code, which assumes that state IDs found on runtime chunk memory are valid and registered.

this problem exists in other places too, and probably requires a rethink of how we're dealing with this, but for now, this will do as a band-aid.
2025-03-15 20:53:49 +00:00
4ef21fabab Merge branch 'stable' into minor-next 2025-03-15 20:37:28 +00:00
4407e585e4 Update composer dependencies (minor-next) 2025-03-15 20:36:39 +00:00
463be36b72 Update composer dependencies 2025-03-15 20:33:47 +00:00
8b57e9007a 👺 2025-03-15 01:33:29 +00:00
e03c586c86 GarbageCollectorManager: promote debug message to info
this has such a big impact on performance that I think this is warranted. Should also make it more obvious what the GC is doing without needing to enable ALL debug info.
2025-03-15 01:29:49 +00:00
802e373bf3 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13868087288
2025-03-15 01:26:47 +00:00
09acbfab4c dependabot: ignore phpstan/phpstan updates
these are noisy and cause conflicts. Since they also usually cause new errors to be reported, we often can't directly update it anyway. Better to test & update this locally.
2025-03-15 00:03:09 +00:00
7cfaf04b87 stfu 2025-03-14 16:10:56 +00:00
d9e0e51e14 Reduce code duplication in copper block serialization handling 2025-03-14 16:08:06 +00:00
069ecf007f Merge branch 'stable' into minor-next 2025-03-14 15:41:46 +00:00
341c7a03a9 CopperMaterial: fixed missing @return $this docs 2025-03-14 15:40:27 +00:00
7ae90dda5d Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13825107599
2025-03-13 01:36:47 +00:00
73a4b076d6 actions: tidy support response message 2025-03-12 16:19:11 +00:00
00df508727 Update bug-report.yml 2025-03-12 13:06:57 +00:00
a6553097f4 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13754675430
2025-03-10 01:14:10 +00:00
afc4a3c7f1 Fixed entity position offset not being included in AddActorPacket (#6643)
this was causing TNT and falling blocks to briefly appear half a block lower than their true position, because their positions are measured from the center and not the base.
2025-03-09 02:09:53 +00:00
ac7b5b3b13 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13743358576
2025-03-09 01:16:28 +00:00
7af5eb3da2 crafting: validate array inputs
this makes sure wrong parameters don't show up as core errors, as seen in crash report 12373907
closes #6642
2025-03-09 01:10:12 +00:00
95284bc9de change error identifier 2025-03-09 00:54:39 +00:00
2291546610 phpstan: added rule to ban new $class
see #6635 for rationale on why we want to get rid of this

for now, this rule will prevent this anti-feature from being used in new code
2025-03-09 00:51:12 +00:00
aad2bce9e4 readme: call out Easy tasks issues 2025-03-09 00:23:59 +00:00
09f0ce458c Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13666455727
2025-03-05 01:27:20 +00:00
50a1e59aa4 5.25.3 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/13662905668
2025-03-04 21:05:10 +00:00
e8824a36b9 Merge pull request #6645 from pmmp/explode-limit
5.25.2
2025-03-04 21:03:52 +00:00
b1e63e544f Merge branch 'stable' into explode-limit 2025-03-04 21:00:40 +00:00
9e9f8a4870 Prepare 5.25.2 release 2025-03-04 20:57:47 +00:00
d0d84d4c51 New rule: explode() limit parameter must be set 2025-03-04 20:44:01 +00:00
9382e6e5b3 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13610829991
2025-03-02 01:38:07 +00:00
e3e0c14275 Bump the github-actions group with 2 updates (#6644) 2025-03-01 10:04:01 +00:00
afac178cf4 Merge branch 'stable' into minor-next 2025-02-26 17:31:55 +00:00
e2f5e3e73c Update composer dependencies 2025-02-26 17:31:26 +00:00
3a2d0d77d1 Update composer dependencies 2025-02-26 17:30:20 +00:00
32b98dcbde draft-release: add a warning about bug reporting
too many people just spam on discord and expect that to somehow do something ...
2025-02-26 17:23:27 +00:00
092ea07d51 5.25.2 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/13549549222
2025-02-26 17:14:49 +00:00
706f391068 Release 5.25.1 (#6641) 2025-02-26 17:13:44 +00:00
7c654271a8 Bump phpstan/phpstan in the development-patch-updates group (#6640) 2025-02-25 13:40:11 +00:00
19425e35ea Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13488997706
2025-02-24 01:26:36 +00:00
3df2bdb879 Fixed door facing
this was broken in 1.21.60 update.

should've known better to expect a blockstate upgrade to mean a
blockstate fix...
2025-02-24 01:04:52 +00:00
1fed9f6cb5 BlockBreakInfo: fixed confusing error message 2025-02-23 20:02:27 +00:00
3050af0bc0 ResourcePackManager: validate pack UUIDs
fixes CrashArchive ##12248760
2025-02-23 19:45:38 +00:00
a08b06d322 also sort table by ID 2025-02-18 15:34:20 +00:00
c876253f76 tools/blockstate-upgrade-schema-utils: added dump-table command
this command dumps a human-readable version of pmmp/mapping palette mapping files to a .txt file.
may be useful for debugging issues with the schema generator or the upgrade process.
2025-02-18 15:30:39 +00:00
67272f8f2b Merge branch 'stable' of github.com:pmmp/PocketMine-MP into minor-next 2025-02-18 01:25:46 +00:00
77be5f8e25 Update PHPStan 2025-02-17 17:51:39 +00:00
9744bd7073 CraftingManager: make a less dumb hash function
fixes #4415
2025-02-17 15:35:18 +00:00
51cf6817b1 World: fixed overflow checks for getCollisionBlocks(), closes #6630 2025-02-16 23:24:39 +00:00
fd04894a7b Merge branch 'stable' of github.com:pmmp/PocketMine-MP into minor-next 2025-02-16 23:18:34 +00:00
d2d6a59c72 ItemDeserializer: fix doc comment typo 2025-02-16 22:52:11 +00:00
788ee9a008 Allow overriding deserializers for block and item IDs
there's no technical reason not to support this, since it doesn't violate any assumptions and the type returned is a base anyway.

this enables implementing stuff like snow cauldrons in a plugin, which previously would require reflection due to the minecraft:cauldron deserializer being registered already.
it also enables overriding IDs to map to custom blocks, which might be useful for overriding some functionality (although this is inadvisable - and won't alter the usage of stuff like VanillaBlocks::WHATEVER()).

we do *not* allow overriding serializers, since type IDs are expected to be paired to block implementations, and allowing them to be reassigned could lead to crashes if the new class was incorrect. So the correct approach for overriding nether portals would be to create a custom type ID as if you were adding a fully custom item. This will also allow other plugins to distinguish between your implementation and the built-in one.
2025-02-16 22:49:40 +00:00
34f801ee3c 5.25.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/13359320328
2025-02-16 22:18:20 +00:00
246c1776df InventoryAction: avoid throwaway Item clones 2025-02-16 21:47:35 +00:00
2670e81668 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into minor-next 2025-02-12 02:14:51 +00:00
e29aa2f337 Added resin material color (#6622) 2025-02-11 16:15:42 +00:00
9b3b45258a Optimise collision box checks by caching some basic info (#6606)
This PR significantly improves performance of entity movement calculation.

Previous attempts to optimise this were ineffective, as they used a cache to mitigate the cost of recomputing AABBs. Entities tend to move around randomly, so the non-cached pathway really needed to be optimized.

This change improves performance on multiple fronts:
1) avoiding Block allocations for blocks with 1x1x1 AABBs and with no AABBs (the most common)
2) avoiding Block allocations and overlapping intersection checks unless a stateID is specifically known to potentially exceed its cell boundaries (like fences)
3) avoiding overlapping AABB checks when overlaps can't make any difference anyway (cubes)

Together, these changes improve the performance of World->getBlockCollisionBoxes() by a factor of 5. In real-world terms, this shows up as a major performance improvement in situations with lots of entities moving in random directions. Testing with item entities showed an increase from 400 to 1200 moving items with the same CPU usage.

This change is built on the assumption that `Block->recalculateCollisionBoxes()` and its overrides don't interact with any world. This is technically possible due to the crappy design of the `Block` architecture, but should be avoided. As a world is not available during `RuntimeBlockStateRegistry` initialization, attempting to interact with a world during `recalculateCollisionBoxes()` will now cause a crash.

This turned out to be a problem for `ChorusPlant`, which was fixed by 70fb9bbdfd. The correct solution in this case was to use dynamic states similar to how we currently deal with fence connections.
2025-02-06 15:42:10 +00:00
ca4debbf08 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13126338048
2025-02-04 01:22:15 +00:00
03f98ee0a9 New version of NBT for performance improvements 2025-02-02 19:47:47 +00:00
70368ea653 Merge branch 'stable' into minor-next 2025-02-02 19:47:03 +00:00
21ccd90147 ChunkCache: parameterize dimension ID
(cc @Muqsit)
2025-02-02 19:43:04 +00:00
0a9a45a126 Improve block break progress
closes #6500

This fixes break time animations for mining fatigue and haste, and
improves the underwater and on-ground behaviour.

on-ground is still not quite right for reasons not related to this PR
(see #6547).
I'm also not quite sure the underwater logic is correct (in water vs
underwater?) but it's definitely better than what we have currently.
2025-02-02 19:34:14 +00:00
88937f1785 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13094301661
2025-02-02 01:26:08 +00:00
e5a783cb9e Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13001707314
2025-01-28 01:21:43 +00:00
70fb9bbdfd ChorusPlant: fixed recalculateCollisionBoxes() depending on the world 2025-01-27 21:28:26 +00:00
84 changed files with 1546 additions and 795 deletions

View File

@ -57,6 +57,9 @@ body:
attributes:
value: |
## Version, OS and game info
> [!WARNING]
> "Latest" is not a valid version.
> Failure to fill these fields with valid information may result in your issue being closed.
- type: input
attributes:

View File

@ -12,6 +12,10 @@ updates:
update-types:
- "version-update:semver-major"
- "version-update:semver-minor"
#since we lock this to exact versions, it causes conflicts with minor-next & major-next in composer.lock
#better to just test updates to this locally anyway since almost every version breaks something
- dependency-name: phpstan/phpstan
groups:
production-patch-updates:
dependency-type: production

View File

@ -8,7 +8,7 @@ on:
jobs:
build:
name: Update Docker Hub images
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Set up Docker Buildx
@ -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.16.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.16.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.16.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.16.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.33.0
with:
php-version: 8.2

View File

@ -24,7 +24,7 @@ permissions:
jobs:
check-intent:
name: Check release trigger
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
outputs:
valid: ${{ steps.validate.outputs.DEV_BUILD == 'false' }}
@ -43,13 +43,13 @@ jobs:
#don't do these checks if this isn't a release - we don't want to generate unnecessary failed statuses
if: needs.check-intent.outputs.valid == 'true'
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@2.32.0
uses: shivammathur/setup-php@2.33.0
with:
php-version: 8.2

View File

@ -23,7 +23,7 @@ env:
jobs:
skip:
name: Check whether to ignore this tag
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
outputs:
skip: ${{ steps.exists.outputs.exists == 'true' }}
@ -54,12 +54,12 @@ jobs:
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
runs-on: ubuntu-22.04
steps:
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@v1
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}
@ -79,7 +79,7 @@ jobs:
needs: [check]
if: needs.check.outputs.valid == 'true' || github.ref_type == 'tag' #ignore validity check for tags
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
@ -87,7 +87,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.32.0
uses: shivammathur/setup-php@2.33.0
with:
php-version: ${{ env.PHP_VERSION }}
@ -165,7 +165,7 @@ jobs:
${{ github.workspace }}/core-permissions.rst
- name: Create draft release
uses: ncipollo/release-action@v1.15.0
uses: ncipollo/release-action@v1.16.0
id: create-draft
with:
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst
@ -182,6 +182,8 @@ jobs:
:information_source: Download the recommended PHP binary [here](${{ steps.php-binary-url.outputs.PHP_BINARY_URL }}).
:warning: Found a bug? Report it on our [issue tracker](${{ github.server_url }}/${{ github.repository }}/issues). **We can't fix bugs if you don't report them.**
- name: Post draft release URL on PR
if: github.event_name == 'pull_request_target'
uses: thollander/actions-comment-pull-request@v3

View File

@ -15,7 +15,7 @@ on:
type: number
image:
description: 'Runner image to use'
default: 'ubuntu-20.04'
default: 'ubuntu-22.04'
type: string
jobs:

View File

@ -20,7 +20,7 @@ jobs:
codestyle:
name: Code Style checks
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
fail-fast: false
@ -28,10 +28,10 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.32.0
uses: shivammathur/setup-php@2.33.0
with:
php-version: 8.2
tools: php-cs-fixer:3.49
php-version: 8.3
tools: php-cs-fixer:3.75
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -40,7 +40,7 @@ jobs:
shellcheck:
name: ShellCheck
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
fail-fast: false

View File

@ -20,10 +20,7 @@ jobs:
- Check our [Documentation](https://doc.pmmp.io) to see if you can find answers there
- Ask the community on our [Discord server](https://discord.gg/bmSAZBG) or our [Forums](https://forums.pmmp.io)
[Docs](https://pmmp.rtfd.io) | [Discord](https://discord.gg/bmSAZBG) | [Forums](https://forums.pmmp.io)
- Ask the community on our [Discord server](https://discord.gg/bmSAZBG)
close-issue: true
lock-issue: false

View File

@ -22,7 +22,7 @@ jobs:
steps:
- name: Generate access token
id: generate-token
uses: actions/create-github-app-token@v1
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.RESTRICTED_ACTIONS_DISPATCH_ID }}
private-key: ${{ secrets.RESTRICTED_ACTIONS_DISPATCH_KEY }}

View File

@ -6,6 +6,12 @@ $finder = PhpCsFixer\Finder::create()
->in(__DIR__ . '/tests')
->in(__DIR__ . '/tools')
->notPath('plugins/DevTools')
//JsonMapper will break if the FQNs in the doc comments for these are shortened :(
->notPath('crafting/json')
->notPath('inventory/json')
->notPath('data/bedrock/block/upgrade/model')
->notPath('data/bedrock/item/upgrade/model')
->notName('PocketMine.php');
return (new PhpCsFixer\Config)

View File

@ -65,6 +65,8 @@ PocketMine-MP accepts community contributions! The following resources will be u
* [Building and running PocketMine-MP from source](BUILDING.md)
* [Contributing Guidelines](CONTRIBUTING.md)
New here? Check out [issues with the "Easy task" label](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Easy%20task%22) for things you could work to familiarise yourself with the codebase.
## Donate
PocketMine-MP is free, but it requires a lot of time and effort from unpaid volunteers to develop. Donations enable us to keep delivering support for new versions and adding features your players love.

View File

@ -31,12 +31,12 @@ require dirname(__DIR__) . '/vendor/autoload.php';
*/
/**
* @var string[]|\Closure[] $options
* @phpstan-var array<string, string|\Closure() : string> $options
* @var string[]|Closure[] $options
* @phpstan-var array<string, string|Closure() : string> $options
*/
$options = [
"base_version" => VersionInfo::BASE_VERSION,
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION, limit: 2)[0],
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
"changelog_file_name" => function() : string{

View File

@ -36,3 +36,17 @@ It also allows creating new collapsible groups of items, and modifying or removi
- `BedrockDataFiles` now includes constants for folders at the top level of `BedrockData` as well as files.
- The structure of creative data in `BedrockData` was changed to accommodate item category and grouping information. `creativeitems.json` has been replaced by `creative/*.json`, which contain information about item grouping and also segregates item lists per category.
- New information was added to `required_item_list.json` in `BedrockData`, as the server is now required to send item component NBT data in some cases.
# 5.25.1
Released 26th February 2025.
## Fixes
- Fixed confusing exception message when a block-breaking tool has an efficiency value of zero.
- Fixed incorrect facing of doors since 1.21.60 (resulted in mismatched AABBs between client & server, rendering glitches etc.)
- Resource pack UUIDs are now validated on load. Previously, invalid UUIDs would be accepted, and potentially cause a server crash on player join.
# 5.25.2
Released 4th March 2025.
## Fixes
- Added limits to various `explode()` calls.

71
changelogs/5.26.md Normal file
View File

@ -0,0 +1,71 @@
# 5.26.0
Released 22nd March 2025.
This is a minor feature release focused on performance improvements.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## Performance
- Significantly improved performance of entity movement. Load testing with item entities showed a 3x increase in the number of entities supported without lag.
- Significantly improved performance of on-ground checks for player movement. This still needs further work, but optimisations implemented in this version should improve performance substantially.
- Updated `pocketmine/nbt` dependency with performance improvements to `TAG_Compound` and `TAG_List` comparison. This should improve performance of inventory-related actions.
- `InventoryTransaction` now avoids useless item clones when processing transactions, which should improve performance of inventory-related actions.
## Dependencies
- `pocketmine/bedrock-protocol` has been updated to `36.2.0`, which adds new functions to access some packet fields.
- `pocketmine/nbt` has been updated to `1.1.0`, which improves performance when comparing NBT object trees.
## Gameplay
- Block breaking animation speed now takes into account the following: jumping, being in water, haste, mining fatigue
## Tools
- `blockstate-upgrade-schema-utils.php` now has a new `dump-table` command, which turns a `.bin` palette table file into human-readable text for debugging.
## API
### `pocketmine\block`
- The following methods have been added:
- `public RuntimeBlockStateRegistry->hasStateId(int $stateId) : bool` - checks whether the given state ID is registered
### `pocketmine\crafting`
- The following methods have been deprecated:
- `CraftingManager::sort()` - this was implicitly internal anyway
### `pocketmine\utils`
- The following constants have been added:
- `TextFormat::MATERIAL_RESIN`
- The following static properties have been added:
- `Terminal::$COLOR_MATERIAL_RESIN`
### `pocketmine\data\bedrock\block`
- `BlockStateToObjectDeserializer` now permits overriding **deserializers** for Bedrock IDs. This may be useful to implement custom state handling, or to implement missing block variants (such as snow cauldron).
- This was originally prohibited since 5.0.0. However, there is no technical reason to disallow overriding **deserializers**.
- Overriding **serializers** is still **not permitted**. Reusing type IDs doesn't make any sense and would break internal design contracts.
- If you want to make a custom version of a vanilla block, create a custom type ID for it, exactly as you would for a regular custom block.
- The following methods have been added:
- `public BlockStateToObjectDeserializer->getDeserializerForId(string $id) : ?(\Closure(BlockStateReader) : Block)`
### `pocketmine\data\bedrock\item`
- `ItemDeserializer` now permits overriding **deserializers** for Bedrock IDs. As above, this may be useful to implement custom data handling, or to implement missing variants of existing items.
- This was originally prohibited since 5.0.0. However, there is no technical reason to disallow overriding **deserializers**.
- Overriding **serializers** is still **not permitted**. Reusing type IDs doesn't make any sense and would break internal design contracts.
- As above, if you want to make a custom version of a vanilla item, create a custom type ID for it, exactly as you would for a regular custom item.
- The following methods have been added:
- `public ItemDeserializer->getDeserializerForId(string $id) : ?(\Closure(SavedItemData) : Item)`
## Internals
- `new $class` is now banned on new internals code by a PHPStan rule. Closures or factory objects should be used instead for greater flexibility and better static analysis.
- `CraftingManager` now uses a more stable hash function for recipe output filtering.
- `ChunkCache` now accepts `int $dimensionId` in the constructor. This may be useful for plugins which implement the nether.
- `RuntimeBlockStateRegistry` now precomputes basic collision info about known states for fast paths.
- This permits specialization for common shapes like cubes and collisionless blocks, which allows skipping complex logic in entity movement calculation. This vastly improves performance.
- Any block whose class overrides `readStateFromWorld()` or `getModelPositionOffset()` will *not* be optimised.
- `Block->recalculateCollisionBoxes()` now has a hard requirement not to depend on anything other than available properties. It must not use `World` or its position.
- This change was problematic for `ChorusPlant`, which used nearby blocks to calculate its collision boxes.
- Blocks which need nearby blocks should override `readStateFromWorld()` and set dynamic state properties, similar to fences.
- This design flaw will be corrected with a major change to `Block` internals currently in planning for a future major version.
- `Block->getCollisionBoxes()` may not be called at all during gameplay for blocks with shapes determined to be simple, like cubes and collisionless blocks.
- `BlockStateToObjectDeserializer` now checks if the returned blockstate is registered in `RuntimeBlockStateRegistry` to promote earlier error detection (instead of crashing in random code paths).

24
changelogs/5.27.md Normal file
View File

@ -0,0 +1,24 @@
# 5.27.0
Released 27th March 2025.
This is a support release for Minecraft: Bedrock Edition 1.21.70.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## Interim releases
If you're upgrading from 5.25.x directly to 5.27.0, please also read the following changelogs, as the interim releases contain important changes:
- [5.26.0](https://github.com/pmmp/PocketMine-MP/blob/5.26.0/changelogs/5.26.md#5260) - Performance improvements and other internal improvements
## General
- Aded support for Minecraft: Bedrock Edition 1.21.70.
- Removed support for earlier versions.
# 5.27.1
Released 6th April 2025.
## Fixes
- Updated RakLib to get ping timestamp handling fixes.

27
changelogs/5.28.md Normal file
View File

@ -0,0 +1,27 @@
# 5.28.0
Released 9th May 2025.
This is a support release for Minecraft: Bedrock Edition 1.21.80.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Added support for Minecraft: Bedrock Edition 1.21.80.
- Removed support for earlier versions.
## Fixes
- `AvailableEnchantmentRegistry` now requires provided tags to always be `string`. Previously, this wasn't enforced, leading to random crashes in core code related to enchanting.
- `Entity->setFireTicks()` and `Entity->setOnFire()` now truncate the fire time to the max value instead of throwing exceptions.
## Internals
- Improved PHPStan error reporting for unsafe foreaches. Foreach on an array with implicit keys now generates different errors than foreach on an array with string keys.
# 5.28.1
Released 17th May 2025.
## Fixes
- Fixed errors when PlayStation players attempt to join due to null `TitleID`.

View File

@ -34,9 +34,9 @@
"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-data": "5.0.0+bedrock-1.21.80",
"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": "38.0.0+bedrock-1.21.80",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0",
@ -44,19 +44,23 @@
"pocketmine/locale-data": "~2.24.0",
"pocketmine/log": "^0.4.0",
"pocketmine/math": "~1.0.0",
"pocketmine/nbt": "~1.0.0",
"pocketmine/raklib": "~1.1.0",
"pocketmine/nbt": "~1.1.0",
"pocketmine/raklib": "~1.1.2",
"pocketmine/raklib-ipc": "~1.0.0",
"pocketmine/snooze": "^0.5.0",
"ramsey/uuid": "~4.7.0",
"symfony/filesystem": "~6.4.0"
},
"require-dev": {
"phpstan/phpstan": "2.1.4",
"phpstan/phpstan": "2.1.16",
"phpstan/phpstan-phpunit": "^2.0.0",
"phpstan/phpstan-strict-rules": "^2.0.0",
"phpunit/phpunit": "^10.5.24"
},
"provide": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-mbstring": "*"
},
"autoload": {
"psr-4": {
"pocketmine\\": "src/"

366
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": "af7547291a131bfac6d7087957601325",
"content-hash": "b25d87be51beaaad7285a6b2e771ab4e",
"packages": [
{
"name": "adhocore/json-comment",
@ -67,16 +67,16 @@
},
{
"name": "brick/math",
"version": "0.12.1",
"version": "0.12.3",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "f510c0a40911935b77b86859eb5223d58d660df1"
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1",
"reference": "f510c0a40911935b77b86859eb5223d58d660df1",
"url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba",
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba",
"shasum": ""
},
"require": {
@ -85,7 +85,7 @@
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^10.1",
"vimeo/psalm": "5.16.0"
"vimeo/psalm": "6.8.8"
},
"type": "library",
"autoload": {
@ -115,7 +115,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.12.1"
"source": "https://github.com/brick/math/tree/0.12.3"
},
"funding": [
{
@ -123,7 +123,7 @@
"type": "github"
}
],
"time": "2023-11-29T23:19:16+00:00"
"time": "2025-02-28T13:11:00+00:00"
},
{
"name": "netresearch/jsonmapper",
@ -204,16 +204,16 @@
},
{
"name": "pocketmine/bedrock-data",
"version": "4.0.0+bedrock-1.21.60",
"version": "5.0.0+bedrock-1.21.80",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e"
"reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/2e5f16ec2facac653f3f894f22eb831d880ba98e",
"reference": "2e5f16ec2facac653f3f894f22eb831d880ba98e",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/e38d5ea19f794ec5216e5f96742237e8c4e7f080",
"reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080",
"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.80"
},
"time": "2025-02-16T15:56:56+00:00"
"time": "2025-05-09T14:15:18+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": "38.0.0+bedrock-1.21.80",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "2057de319c5c551001c2a544e08d1bc7727d9963"
"reference": "a626561eaefeb6333c0d2726e2789ceb0aac0724"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2057de319c5c551001c2a544e08d1bc7727d9963",
"reference": "2057de319c5c551001c2a544e08d1bc7727d9963",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a626561eaefeb6333c0d2726e2789ceb0aac0724",
"reference": "a626561eaefeb6333c0d2726e2789ceb0aac0724",
"shasum": ""
},
"require": {
@ -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/38.0.0+bedrock-1.21.80"
},
"time": "2025-02-16T15:59:08+00:00"
"time": "2025-05-09T14:17:07+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -471,16 +471,16 @@
},
{
"name": "pocketmine/locale-data",
"version": "2.24.0",
"version": "2.24.2",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Language.git",
"reference": "6ec5e92c77a2102b2692763733e4763012facae9"
"reference": "2a00c44c52bce98e7a43aa31517df78cbb2ba23b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/6ec5e92c77a2102b2692763733e4763012facae9",
"reference": "6ec5e92c77a2102b2692763733e4763012facae9",
"url": "https://api.github.com/repos/pmmp/Language/zipball/2a00c44c52bce98e7a43aa31517df78cbb2ba23b",
"reference": "2a00c44c52bce98e7a43aa31517df78cbb2ba23b",
"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.24.2"
},
"time": "2025-02-16T20:46:34+00:00"
"time": "2025-04-03T01:23:27+00:00"
},
{
"name": "pocketmine/log",
@ -576,16 +576,16 @@
},
{
"name": "pocketmine/nbt",
"version": "1.0.1",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/NBT.git",
"reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d"
"reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/53db37487bc5ddbfbd84247966e1a073bdcfdb7d",
"reference": "53db37487bc5ddbfbd84247966e1a073bdcfdb7d",
"url": "https://api.github.com/repos/pmmp/NBT/zipball/c3c7b0a7295daeaf7873d90fed5c5d10381d12e1",
"reference": "c3c7b0a7295daeaf7873d90fed5c5d10381d12e1",
"shasum": ""
},
"require": {
@ -612,22 +612,22 @@
"description": "PHP library for working with Named Binary Tags",
"support": {
"issues": "https://github.com/pmmp/NBT/issues",
"source": "https://github.com/pmmp/NBT/tree/1.0.1"
"source": "https://github.com/pmmp/NBT/tree/1.1.1"
},
"time": "2025-01-07T22:47:46+00:00"
"time": "2025-03-09T01:46:03+00:00"
},
{
"name": "pocketmine/raklib",
"version": "1.1.1",
"version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/pmmp/RakLib.git",
"reference": "be2783be516bf6e2872ff5c81fb9048596617b97"
"reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/be2783be516bf6e2872ff5c81fb9048596617b97",
"reference": "be2783be516bf6e2872ff5c81fb9048596617b97",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/4145a31cd812fe8931c3c9c691fcd2ded2f47e7f",
"reference": "4145a31cd812fe8931c3c9c691fcd2ded2f47e7f",
"shasum": ""
},
"require": {
@ -639,8 +639,8 @@
"pocketmine/log": "^0.3.0 || ^0.4.0"
},
"require-dev": {
"phpstan/phpstan": "1.10.1",
"phpstan/phpstan-strict-rules": "^1.0"
"phpstan/phpstan": "2.1.0",
"phpstan/phpstan-strict-rules": "^2.0"
},
"type": "library",
"autoload": {
@ -655,9 +655,9 @@
"description": "A RakNet server implementation written in PHP",
"support": {
"issues": "https://github.com/pmmp/RakLib/issues",
"source": "https://github.com/pmmp/RakLib/tree/1.1.1"
"source": "https://github.com/pmmp/RakLib/tree/1.1.2"
},
"time": "2024-03-04T14:02:14+00:00"
"time": "2025-04-06T03:38:21+00:00"
},
{
"name": "pocketmine/raklib-ipc",
@ -742,16 +742,16 @@
},
{
"name": "ramsey/collection",
"version": "2.0.0",
"version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/ramsey/collection.git",
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5"
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
"url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2",
"reference": "344572933ad0181accbf4ba763e85a0306a8c5e2",
"shasum": ""
},
"require": {
@ -759,25 +759,22 @@
},
"require-dev": {
"captainhook/plugin-composer": "^5.3",
"ergebnis/composer-normalize": "^2.28.3",
"fakerphp/faker": "^1.21",
"ergebnis/composer-normalize": "^2.45",
"fakerphp/faker": "^1.24",
"hamcrest/hamcrest-php": "^2.0",
"jangregor/phpstan-prophecy": "^1.0",
"mockery/mockery": "^1.5",
"jangregor/phpstan-prophecy": "^2.1",
"mockery/mockery": "^1.6",
"php-parallel-lint/php-console-highlighter": "^1.0",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5",
"psalm/plugin-mockery": "^1.1",
"psalm/plugin-phpunit": "^0.18.4",
"ramsey/coding-standard": "^2.0.3",
"ramsey/conventional-commits": "^1.3",
"vimeo/psalm": "^5.4"
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpspec/prophecy-phpunit": "^2.3",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-mockery": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^10.5",
"ramsey/coding-standard": "^2.3",
"ramsey/conventional-commits": "^1.6",
"roave/security-advisories": "dev-latest"
},
"type": "library",
"extra": {
@ -815,19 +812,9 @@
],
"support": {
"issues": "https://github.com/ramsey/collection/issues",
"source": "https://github.com/ramsey/collection/tree/2.0.0"
"source": "https://github.com/ramsey/collection/tree/2.1.1"
},
"funding": [
{
"url": "https://github.com/ramsey",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
"type": "tidelift"
}
],
"time": "2022-12-31T21:50:55+00:00"
"time": "2025-03-22T05:38:12+00:00"
},
{
"name": "ramsey/uuid",
@ -986,180 +973,21 @@
}
],
"time": "2024-10-25T15:07:50+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
},
"suggest": {
"ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
}
],
"packages-dev": [
{
"name": "myclabs/deep-copy",
"version": "1.12.1",
"version": "1.13.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
"reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
"shasum": ""
},
"require": {
@ -1198,7 +1026,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.1"
"source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
},
"funding": [
{
@ -1206,7 +1034,7 @@
"type": "tidelift"
}
],
"time": "2024-11-08T17:47:46+00:00"
"time": "2025-04-29T12:36:36+00:00"
},
{
"name": "nikic/php-parser",
@ -1386,16 +1214,16 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.4",
"version": "2.1.16",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "8f99e18eb775dbaf6460c95fa0b65312da9c746a"
"reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8f99e18eb775dbaf6460c95fa0b65312da9c746a",
"reference": "8f99e18eb775dbaf6460c95fa0b65312da9c746a",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9",
"reference": "b8c1cf533cba0c305d91c6ccd23f3dd0566ba5f9",
"shasum": ""
},
"require": {
@ -1440,20 +1268,20 @@
"type": "github"
}
],
"time": "2025-02-10T08:25:21+00:00"
"time": "2025-05-16T09:40:10+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
"version": "2.0.4",
"version": "2.0.6",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
"reference": "d09e152f403c843998d7a52b5d87040c937525dd"
"reference": "6b92469f8a7995e626da3aa487099617b8dfa260"
},
"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/6b92469f8a7995e626da3aa487099617b8dfa260",
"reference": "6b92469f8a7995e626da3aa487099617b8dfa260",
"shasum": ""
},
"require": {
@ -1464,7 +1292,9 @@
"phpunit/phpunit": "<7.0"
},
"require-dev": {
"nikic/php-parser": "^5",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^9.6"
},
@ -1489,22 +1319,22 @@
"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/2.0.6"
},
"time": "2025-01-22T13:07:38+00:00"
"time": "2025-03-26T12:47:06+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "2.0.3",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba"
"reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a"
},
"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/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
"reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
"shasum": ""
},
"require": {
@ -1537,9 +1367,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/2.0.4"
},
"time": "2025-01-21T10:52:14+00:00"
"time": "2025-03-18T11:42:40+00:00"
},
{
"name": "phpunit/php-code-coverage",
@ -1864,16 +1694,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.44",
"version": "10.5.46",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36"
"reference": "8080be387a5be380dda48c6f41cee4a13aadab3d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1381c62769be4bb88fa4c5aec1366c7c66ca4f36",
"reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8080be387a5be380dda48c6f41cee4a13aadab3d",
"reference": "8080be387a5be380dda48c6f41cee4a13aadab3d",
"shasum": ""
},
"require": {
@ -1883,7 +1713,7 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.12.1",
"myclabs/deep-copy": "^1.13.1",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.1",
@ -1945,7 +1775,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.44"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.46"
},
"funding": [
{
@ -1956,12 +1786,20 @@
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
"type": "tidelift"
}
],
"time": "2025-01-31T07:00:38+00:00"
"time": "2025-05-02T06:46:24+00:00"
},
{
"name": "sebastian/cli-parser",
@ -2932,7 +2770,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@ -2963,7 +2801,7 @@
"ext-zlib": ">=1.2.11",
"composer-runtime-api": "^2.0"
},
"platform-dev": [],
"platform-dev": {},
"platform-overrides": {
"php": "8.1.0"
},

View File

@ -11,9 +11,11 @@ includes:
rules:
- pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule
- pocketmine\phpstan\rules\DisallowDynamicNewRule
- pocketmine\phpstan\rules\DisallowEnumComparisonRule
- pocketmine\phpstan\rules\DisallowForeachByReferenceRule
- pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule
- pocketmine\phpstan\rules\ExplodeLimitRule
- pocketmine\phpstan\rules\UnsafeForeachRule
# - pocketmine\phpstan\rules\ThreadedSupportedTypesRule
parameters:

View File

@ -31,6 +31,7 @@ use function hrtime;
use function max;
use function min;
use function number_format;
use function sprintf;
/**
* Allows threads to manually trigger the cyclic garbage collector using a threshold like PHP's own garbage collector,
@ -48,6 +49,7 @@ final class GarbageCollectorManager{
private int $threshold = self::GC_THRESHOLD_DEFAULT;
private int $collectionTimeTotalNs = 0;
private int $runs = 0;
private \Logger $logger;
private TimingsHandler $timings;
@ -96,7 +98,16 @@ final class GarbageCollectorManager{
$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");
$this->runs++;
$this->logger->info(sprintf(
"Run #%d took %s ms (%s -> %s roots, %s cycles collected) - cumulative GC time: %s ms",
$this->runs,
number_format($time / 1_000_000, 2),
$rootsBefore,
$rootsAfter,
$cycles,
number_format($this->collectionTimeTotalNs / 1_000_000, 2)
));
return $cycles;
}

View File

@ -264,7 +264,7 @@ JIT_WARNING
$composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
if($composerGitHash !== null){
//we can't verify dependency versions if we were installed without using git
$currentGitHash = explode("-", VersionInfo::GIT_HASH())[0];
$currentGitHash = explode("-", VersionInfo::GIT_HASH(), 2)[0];
if($currentGitHash !== $composerGitHash){
critical_error("Composer dependencies and/or autoloader are out of sync.");
critical_error("- Current revision is $currentGitHash");

View File

@ -699,7 +699,7 @@ class Server{
public function removeOp(string $name) : void{
$lowercaseName = strtolower($name);
foreach($this->operators->getAll() as $operatorName => $_){
foreach(Utils::promoteKeys($this->operators->getAll()) as $operatorName => $_){
$operatorName = (string) $operatorName;
if($lowercaseName === strtolower($operatorName)){
$this->operators->remove($operatorName);

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.25.0";
public const BASE_VERSION = "5.28.1";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_CHANNEL = "stable";

View File

@ -154,7 +154,7 @@ class BlockBreakInfo{
$efficiency = $item->getMiningEfficiency(($this->toolType & $item->getBlockToolType()) !== 0);
if($efficiency <= 0){
throw new \InvalidArgumentException(get_class($item) . " has invalid mining efficiency: expected >= 0, got $efficiency");
throw new \InvalidArgumentException(get_class($item) . " must have a positive mining efficiency, but got $efficiency");
}
$base /= $efficiency;

View File

@ -34,11 +34,16 @@ use function mt_rand;
final class ChorusPlant extends Flowable{
use StaticSupportTrait;
/**
* @var true[]
* @phpstan-var array<int, true>
*/
protected array $connections = [];
protected function recalculateCollisionBoxes() : array{
$bb = AxisAlignedBB::one();
foreach($this->getAllSides() as $facing => $block){
$id = $block->getTypeId();
if($id !== BlockTypeIds::END_STONE && $id !== BlockTypeIds::CHORUS_FLOWER && !$block->hasSameTypeId($this)){
foreach(Facing::ALL as $facing){
if(!isset($this->connections[$facing])){
$bb->trim($facing, 2 / 16);
}
}
@ -46,6 +51,26 @@ final class ChorusPlant extends Flowable{
return [$bb];
}
public function readStateFromWorld() : Block{
parent::readStateFromWorld();
$this->collisionBoxes = null;
foreach(Facing::ALL as $facing){
$block = $this->getSide($facing);
if(match($block->getTypeId()){
BlockTypeIds::END_STONE, BlockTypeIds::CHORUS_FLOWER, $this->getTypeId() => true,
default => false
}){
$this->connections[$facing] = true;
}else{
unset($this->connections[$facing]);
}
}
return $this;
}
private function canBeSupportedBy(Block $block) : bool{
return $block->hasSameTypeId($this) || $block->getTypeId() === BlockTypeIds::END_STONE;
}

View File

@ -28,6 +28,7 @@ use pocketmine\block\BlockIdentifier as BID;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
use pocketmine\world\light\LightUpdate;
use function count;
use function min;
/**
@ -40,6 +41,11 @@ use function min;
class RuntimeBlockStateRegistry{
use SingletonTrait;
public const COLLISION_CUSTOM = 0;
public const COLLISION_CUBE = 1;
public const COLLISION_NONE = 2;
public const COLLISION_MAY_OVERFLOW = 3;
/**
* @var Block[]
* @phpstan-var array<int, Block>
@ -74,6 +80,13 @@ class RuntimeBlockStateRegistry{
*/
public array $blastResistance = [];
/**
* Map of state ID -> useful AABB info to avoid unnecessary block allocations
* @var int[]
* @phpstan-var array<int, int>
*/
public array $collisionInfo = [];
public function __construct(){
foreach(VanillaBlocks::getAll() as $block){
$this->register($block);
@ -100,6 +113,70 @@ class RuntimeBlockStateRegistry{
}
}
/**
* Checks if the given class method overrides a method in Block.
* Used to determine if a block might need to disable fast path optimizations.
*
* @phpstan-param anyClosure $closure
*/
private static function overridesBlockMethod(\Closure $closure) : bool{
$declarer = (new \ReflectionFunction($closure))->getClosureScopeClass();
return $declarer !== null && $declarer->getName() !== Block::class;
}
/**
* A big ugly hack to set up fast paths for handling collisions on blocks with common shapes.
* The information returned here is stored in RuntimeBlockStateRegistry->collisionInfo, and is used during entity
* collision box calculations to avoid complex logic and unnecessary block object allocations.
* This hack allows significant performance improvements.
*
* TODO: We'll want to redesign block collision box handling and block shapes in the future, but that's a job for a
* major version. For now, this hack nets major performance wins.
*/
private static function calculateCollisionInfo(Block $block) : int{
if(
self::overridesBlockMethod($block->getModelPositionOffset(...)) ||
self::overridesBlockMethod($block->readStateFromWorld(...))
){
//getModelPositionOffset() might cause AABBs to shift outside the cell
//readStateFromWorld() might cause overflow in ways we can't predict just by looking at known states
//TODO: excluding overriders of readStateFromWorld() also excludes blocks with tiles that don't do anything
//weird with their AABBs, but for now this is the best we can do.
return self::COLLISION_MAY_OVERFLOW;
}
//TODO: this could blow up if any recalculateCollisionBoxes() uses the world
//it shouldn't, but that doesn't mean that custom blocks won't...
$boxes = $block->getCollisionBoxes();
if(count($boxes) === 0){
return self::COLLISION_NONE;
}
if(
count($boxes) === 1 &&
$boxes[0]->minX === 0.0 &&
$boxes[0]->minY === 0.0 &&
$boxes[0]->minZ === 0.0 &&
$boxes[0]->maxX === 1.0 &&
$boxes[0]->maxY === 1.0 &&
$boxes[0]->maxZ === 1.0
){
return self::COLLISION_CUBE;
}
foreach($boxes as $box){
if(
$box->minX < 0 || $box->maxX > 1 ||
$box->minY < 0 || $box->maxY > 1 ||
$box->minZ < 0 || $box->maxZ > 1
){
return self::COLLISION_MAY_OVERFLOW;
}
}
return self::COLLISION_CUSTOM;
}
private function fillStaticArrays(int $index, Block $block) : void{
$fullId = $block->getStateId();
if($index !== $fullId){
@ -112,6 +189,8 @@ class RuntimeBlockStateRegistry{
if($block->blocksDirectSkyLight()){
$this->blocksDirectSkyLight[$index] = true;
}
$this->collisionInfo[$index] = self::calculateCollisionInfo($block);
}
}
@ -130,6 +209,10 @@ class RuntimeBlockStateRegistry{
return $block;
}
public function hasStateId(int $stateId) : bool{
return isset($this->fullList[$stateId]);
}
/**
* @return Block[]
* @phpstan-return array<int, Block>

View File

@ -62,9 +62,10 @@ class Sign extends Spawnable{
/**
* @return string[]
* @deprecated
*/
public static function fixTextBlob(string $blob) : array{
return array_slice(array_pad(explode("\n", $blob), 4, ""), 0, 4);
return array_slice(array_pad(explode("\n", $blob, limit: 5), 4, ""), 0, 4);
}
protected SignText $text;

View File

@ -32,7 +32,7 @@ use pocketmine\network\mcpe\protocol\types\CacheableNbt;
use function get_class;
abstract class Spawnable extends Tile{
/** @phpstan-var CacheableNbt<\pocketmine\nbt\tag\CompoundTag>|null */
/** @phpstan-var CacheableNbt<CompoundTag>|null */
private ?CacheableNbt $spawnCompoundCache = null;
/**
@ -73,7 +73,7 @@ abstract class Spawnable extends Tile{
* Returns encoded NBT (varint, little-endian) used to spawn this tile to clients. Uses cache where possible,
* populates cache if it is null.
*
* @phpstan-return CacheableNbt<\pocketmine\nbt\tag\CompoundTag>
* @phpstan-return CacheableNbt<CompoundTag>
*/
final public function getSerializedSpawnCompound() : CacheableNbt{
if($this->spawnCompoundCache === null){

View File

@ -30,9 +30,15 @@ interface CopperMaterial{
public function getOxidation() : CopperOxidation;
/**
* @return $this
*/
public function setOxidation(CopperOxidation $oxidation) : CopperMaterial;
public function isWaxed() : bool;
/**
* @return $this
*/
public function setWaxed(bool $waxed) : CopperMaterial;
}

View File

@ -79,7 +79,7 @@ class SignText{
* @throws \InvalidArgumentException if the text is not valid UTF-8
*/
public static function fromBlob(string $blob, ?Color $baseColor = null, bool $glowing = false) : SignText{
return new self(array_slice(array_pad(explode("\n", $blob), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing);
return new self(array_slice(array_pad(explode("\n", $blob, limit: self::LINE_COUNT + 1), self::LINE_COUNT, ""), 0, self::LINE_COUNT), $baseColor, $glowing);
}
/**

View File

@ -37,6 +37,7 @@ use function array_values;
use function explode;
use function implode;
use function str_replace;
use const PHP_INT_MAX;
abstract class Command{
@ -113,7 +114,7 @@ abstract class Command{
}
public function setPermission(?string $permission) : void{
$this->setPermissions($permission === null ? [] : explode(";", $permission));
$this->setPermissions($permission === null ? [] : explode(";", $permission, limit: PHP_INT_MAX));
}
public function testPermission(CommandSender $target, ?string $permission = null) : bool{

View File

@ -39,6 +39,7 @@ use function ksort;
use function min;
use function sort;
use function strtolower;
use const PHP_INT_MAX;
use const SORT_FLAG_CASE;
use const SORT_NATURAL;
@ -108,7 +109,7 @@ class HelpCommand extends VanillaCommand{
$usage = $cmd->getUsage();
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString)))
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString, limit: PHP_INT_MAX)))
->prefix(TextFormat::GOLD));
$aliases = $cmd->getAliases();

View File

@ -219,7 +219,11 @@ class ParticleCommand extends VanillaCommand{
break;
case "blockdust":
if($data !== null){
$d = explode("_", $data);
//to preserve the old unlimited explode behaviour, allow this to split into at most 5 parts
//this allows the 4th argument to be processed normally if given without forcing it to also consume
//any unexpected parts
//we probably ought to error in this case, but this will do for now
$d = explode("_", $data, limit: 5);
if(count($d) >= 3){
return new DustParticle(new Color(
((int) $d[0]) & 0xff,

View File

@ -62,7 +62,7 @@ class ConsoleCommandSender implements CommandSender{
$message = $this->getLanguage()->translate($message);
}
foreach(explode("\n", trim($message)) as $line){
foreach(explode("\n", trim($message), limit: PHP_INT_MAX) as $line){
Terminal::writeLine(TextFormat::GREEN . "Command output | " . TextFormat::addBase(TextFormat::WHITE, $line));
}
}

View File

@ -29,8 +29,12 @@ use pocketmine\nbt\TreeRoot;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\DestructorCallbackTrait;
use pocketmine\utils\ObjectSet;
use function array_shift;
use function count;
use function implode;
use function ksort;
use function spl_object_id;
use function usort;
use const SORT_STRING;
class CraftingManager{
use DestructorCallbackTrait;
@ -100,6 +104,7 @@ class CraftingManager{
/**
* Function used to arrange Shapeless Recipe ingredient lists into a consistent order.
* @deprecated
*/
public static function sort(Item $i1, Item $i2) : int{
//Use spaceship operator to compare each property, then try the next one if they are equivalent.
@ -108,45 +113,30 @@ class CraftingManager{
return $retval;
}
/**
* @param Item[] $items
*
* @return Item[]
* @phpstan-return list<Item>
*/
private static function pack(array $items) : array{
$result = [];
private static function hashOutput(Item $output) : string{
$write = new BinaryStream();
$write->putVarInt($output->getStateId());
$write->put((new LittleEndianNbtSerializer())->write(new TreeRoot($output->getNamedTag())));
foreach($items as $item){
foreach($result as $otherItem){
if($item->canStackWith($otherItem)){
$otherItem->setCount($otherItem->getCount() + $item->getCount());
continue 2;
}
}
//No matching item found
$result[] = clone $item;
}
return $result;
return $write->getBuffer();
}
/**
* @param Item[] $outputs
*/
private static function hashOutputs(array $outputs) : string{
$outputs = self::pack($outputs);
usort($outputs, [self::class, "sort"]);
$result = new BinaryStream();
if(count($outputs) === 1){
return self::hashOutput(array_shift($outputs));
}
$unique = [];
foreach($outputs as $o){
//count is not written because the outputs might be from multiple repetitions of a single recipe
//this reduces the accuracy of the hash, but it won't matter in most cases.
$result->putVarInt($o->getStateId());
$result->put((new LittleEndianNbtSerializer())->write(new TreeRoot($o->getNamedTag())));
$hash = self::hashOutput($o);
$unique[$hash] = $hash;
}
return $result->getBuffer();
ksort($unique, SORT_STRING);
return implode("", $unique);
}
/**

View File

@ -97,6 +97,7 @@ class ShapedRecipe implements CraftingRecipe{
$this->shape = $shape;
Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{});
foreach(Utils::stringifyKeys($ingredients) as $char => $i){
if(!str_contains(implode($this->shape), $char)){
throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape");
@ -105,6 +106,7 @@ class ShapedRecipe implements CraftingRecipe{
$this->ingredientList[$char] = clone $i;
}
Utils::validateArrayValueType($results, function(Item $_) : void{});
$this->results = Utils::cloneObjectArray($results);
}

View File

@ -53,7 +53,9 @@ class ShapelessRecipe implements CraftingRecipe{
if(count($ingredients) > 9){
throw new \InvalidArgumentException("Shapeless recipes cannot have more than 9 ingredients");
}
Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{});
$this->ingredients = $ingredients;
Utils::validateArrayValueType($results, function(Item $_) : void{});
$this->results = Utils::cloneObjectArray($results);
}

View File

@ -31,8 +31,7 @@ final class BedrockDataFiles{
}
public const BANNER_PATTERNS_JSON = BEDROCK_DATA_PATH . '/banner_patterns.json';
public const BIOME_DEFINITIONS_NBT = BEDROCK_DATA_PATH . '/biome_definitions.nbt';
public const BIOME_DEFINITIONS_FULL_NBT = BEDROCK_DATA_PATH . '/biome_definitions_full.nbt';
public const BIOME_DEFINITIONS_JSON = BEDROCK_DATA_PATH . '/biome_definitions.json';
public const BIOME_ID_MAP_JSON = BEDROCK_DATA_PATH . '/biome_id_map.json';
public const BLOCK_ID_TO_ITEM_ID_MAP_JSON = BEDROCK_DATA_PATH . '/block_id_to_item_id_map.json';
public const BLOCK_PROPERTIES_TABLE_JSON = BEDROCK_DATA_PATH . '/block_properties_table.json';

View File

@ -113,6 +113,7 @@ final class BlockStateNames{
public const RAIL_DATA_BIT = "rail_data_bit";
public const RAIL_DIRECTION = "rail_direction";
public const REDSTONE_SIGNAL = "redstone_signal";
public const REHYDRATION_LEVEL = "rehydration_level";
public const REPEATER_DELAY = "repeater_delay";
public const RESPAWN_ANCHOR_CHARGE = "respawn_anchor_charge";
public const ROTATION = "rotation";

View File

@ -175,7 +175,9 @@ final class BlockTypeNames{
public const BUBBLE_CORAL_FAN = "minecraft:bubble_coral_fan";
public const BUBBLE_CORAL_WALL_FAN = "minecraft:bubble_coral_wall_fan";
public const BUDDING_AMETHYST = "minecraft:budding_amethyst";
public const BUSH = "minecraft:bush";
public const CACTUS = "minecraft:cactus";
public const CACTUS_FLOWER = "minecraft:cactus_flower";
public const CAKE = "minecraft:cake";
public const CALCITE = "minecraft:calcite";
public const CALIBRATED_SCULK_SENSOR = "minecraft:calibrated_sculk_sensor";
@ -390,6 +392,7 @@ final class BlockTypeNames{
public const DOUBLE_CUT_COPPER_SLAB = "minecraft:double_cut_copper_slab";
public const DRAGON_EGG = "minecraft:dragon_egg";
public const DRAGON_HEAD = "minecraft:dragon_head";
public const DRIED_GHAST = "minecraft:dried_ghast";
public const DRIED_KELP_BLOCK = "minecraft:dried_kelp_block";
public const DRIPSTONE_BLOCK = "minecraft:dripstone_block";
public const DROPPER = "minecraft:dropper";
@ -545,6 +548,7 @@ final class BlockTypeNames{
public const FIRE_CORAL_BLOCK = "minecraft:fire_coral_block";
public const FIRE_CORAL_FAN = "minecraft:fire_coral_fan";
public const FIRE_CORAL_WALL_FAN = "minecraft:fire_coral_wall_fan";
public const FIREFLY_BUSH = "minecraft:firefly_bush";
public const FLETCHING_TABLE = "minecraft:fletching_table";
public const FLOWER_POT = "minecraft:flower_pot";
public const FLOWERING_AZALEA = "minecraft:flowering_azalea";
@ -685,6 +689,7 @@ final class BlockTypeNames{
public const LARGE_AMETHYST_BUD = "minecraft:large_amethyst_bud";
public const LARGE_FERN = "minecraft:large_fern";
public const LAVA = "minecraft:lava";
public const LEAF_LITTER = "minecraft:leaf_litter";
public const LECTERN = "minecraft:lectern";
public const LEVER = "minecraft:lever";
public const LIGHT_BLOCK_0 = "minecraft:light_block_0";
@ -1043,6 +1048,7 @@ final class BlockTypeNames{
public const SEA_LANTERN = "minecraft:sea_lantern";
public const SEA_PICKLE = "minecraft:sea_pickle";
public const SEAGRASS = "minecraft:seagrass";
public const SHORT_DRY_GRASS = "minecraft:short_dry_grass";
public const SHORT_GRASS = "minecraft:short_grass";
public const SHROOMLIGHT = "minecraft:shroomlight";
public const SILVER_GLAZED_TERRACOTTA = "minecraft:silver_glazed_terracotta";
@ -1140,6 +1146,7 @@ final class BlockTypeNames{
public const SUSPICIOUS_GRAVEL = "minecraft:suspicious_gravel";
public const SUSPICIOUS_SAND = "minecraft:suspicious_sand";
public const SWEET_BERRY_BUSH = "minecraft:sweet_berry_bush";
public const TALL_DRY_GRASS = "minecraft:tall_dry_grass";
public const TALL_GRASS = "minecraft:tall_grass";
public const TARGET = "minecraft:target";
public const TINTED_GLASS = "minecraft:tinted_glass";
@ -1267,6 +1274,7 @@ final class BlockTypeNames{
public const WHITE_TERRACOTTA = "minecraft:white_terracotta";
public const WHITE_TULIP = "minecraft:white_tulip";
public const WHITE_WOOL = "minecraft:white_wool";
public const WILDFLOWERS = "minecraft:wildflowers";
public const WITHER_ROSE = "minecraft:wither_rose";
public const WITHER_SKELETON_SKULL = "minecraft:wither_skeleton_skull";
public const WOODEN_BUTTON = "minecraft:wooden_button";

View File

@ -214,6 +214,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->registerLeavesSerializers();
$this->registerSaplingSerializers();
$this->registerMobHeadSerializers();
$this->registerCopperSerializers();
$this->registerSimpleSerializers();
$this->registerSerializers();
}
@ -791,6 +792,178 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
})->writeFacingWithoutDown($block->getFacing()));
}
private function registerCopperSerializers() : void{
$this->map(Blocks::COPPER(), function(Copper $block) : Writer{
$oxidation = $block->getOxidation();
return new Writer($block->isWaxed() ?
Helper::selectCopperId($oxidation, Ids::WAXED_COPPER, Ids::WAXED_EXPOSED_COPPER, Ids::WAXED_WEATHERED_COPPER, Ids::WAXED_OXIDIZED_COPPER) :
Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER)
);
});
$this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : Writer{
$oxidation = $block->getOxidation();
return new Writer($block->isWaxed() ?
Helper::selectCopperId($oxidation,
Ids::WAXED_CHISELED_COPPER,
Ids::WAXED_EXPOSED_CHISELED_COPPER,
Ids::WAXED_WEATHERED_CHISELED_COPPER,
Ids::WAXED_OXIDIZED_CHISELED_COPPER
) :
Helper::selectCopperId($oxidation,
Ids::CHISELED_COPPER,
Ids::EXPOSED_CHISELED_COPPER,
Ids::WEATHERED_CHISELED_COPPER,
Ids::OXIDIZED_CHISELED_COPPER
)
);
});
$this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : Writer{
$oxidation = $block->getOxidation();
return new Writer($block->isWaxed() ?
Helper::selectCopperId($oxidation,
Ids::WAXED_COPPER_GRATE,
Ids::WAXED_EXPOSED_COPPER_GRATE,
Ids::WAXED_WEATHERED_COPPER_GRATE,
Ids::WAXED_OXIDIZED_COPPER_GRATE
) :
Helper::selectCopperId($oxidation,
Ids::COPPER_GRATE,
Ids::EXPOSED_COPPER_GRATE,
Ids::WEATHERED_COPPER_GRATE,
Ids::OXIDIZED_COPPER_GRATE
)
);
});
$this->map(Blocks::CUT_COPPER(), function(Copper $block) : Writer{
$oxidation = $block->getOxidation();
return new Writer($block->isWaxed() ?
Helper::selectCopperId($oxidation, Ids::WAXED_CUT_COPPER, Ids::WAXED_EXPOSED_CUT_COPPER, Ids::WAXED_WEATHERED_CUT_COPPER, Ids::WAXED_OXIDIZED_CUT_COPPER) :
Helper::selectCopperId($oxidation, Ids::CUT_COPPER, Ids::EXPOSED_CUT_COPPER, Ids::WEATHERED_CUT_COPPER, Ids::OXIDIZED_CUT_COPPER)
);
});
$this->map(Blocks::CUT_COPPER_SLAB(), function(CopperSlab $block) : Writer{
$oxidation = $block->getOxidation();
return Helper::encodeSlab(
$block,
($block->isWaxed() ?
Helper::selectCopperId(
$oxidation,
Ids::WAXED_CUT_COPPER_SLAB,
Ids::WAXED_EXPOSED_CUT_COPPER_SLAB,
Ids::WAXED_WEATHERED_CUT_COPPER_SLAB,
Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB
) :
Helper::selectCopperId(
$oxidation,
Ids::CUT_COPPER_SLAB,
Ids::EXPOSED_CUT_COPPER_SLAB,
Ids::WEATHERED_CUT_COPPER_SLAB,
Ids::OXIDIZED_CUT_COPPER_SLAB
)
),
($block->isWaxed() ?
Helper::selectCopperId(
$oxidation,
Ids::WAXED_DOUBLE_CUT_COPPER_SLAB,
Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB,
Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB,
Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB
) :
Helper::selectCopperId(
$oxidation,
Ids::DOUBLE_CUT_COPPER_SLAB,
Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB,
Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB,
Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB
)
)
);
});
$this->map(Blocks::CUT_COPPER_STAIRS(), function(CopperStairs $block) : Writer{
$oxidation = $block->getOxidation();
return Helper::encodeStairs(
$block,
new Writer($block->isWaxed() ?
Helper::selectCopperId(
$oxidation,
Ids::WAXED_CUT_COPPER_STAIRS,
Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS,
Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS,
Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS
) :
Helper::selectCopperId(
$oxidation,
Ids::CUT_COPPER_STAIRS,
Ids::EXPOSED_CUT_COPPER_STAIRS,
Ids::WEATHERED_CUT_COPPER_STAIRS,
Ids::OXIDIZED_CUT_COPPER_STAIRS
)
)
);
});
$this->map(Blocks::COPPER_BULB(), function(CopperBulb $block) : Writer{
$oxidation = $block->getOxidation();
return Writer::create($block->isWaxed() ?
Helper::selectCopperId($oxidation,
Ids::WAXED_COPPER_BULB,
Ids::WAXED_EXPOSED_COPPER_BULB,
Ids::WAXED_WEATHERED_COPPER_BULB,
Ids::WAXED_OXIDIZED_COPPER_BULB) :
Helper::selectCopperId($oxidation,
Ids::COPPER_BULB,
Ids::EXPOSED_COPPER_BULB,
Ids::WEATHERED_COPPER_BULB,
Ids::OXIDIZED_COPPER_BULB
))
->writeBool(StateNames::LIT, $block->isLit())
->writeBool(StateNames::POWERED_BIT, $block->isPowered());
});
$this->map(Blocks::COPPER_DOOR(), function(CopperDoor $block) : Writer{
$oxidation = $block->getOxidation();
return Helper::encodeDoor(
$block,
new Writer($block->isWaxed() ?
Helper::selectCopperId(
$oxidation,
Ids::WAXED_COPPER_DOOR,
Ids::WAXED_EXPOSED_COPPER_DOOR,
Ids::WAXED_WEATHERED_COPPER_DOOR,
Ids::WAXED_OXIDIZED_COPPER_DOOR
) :
Helper::selectCopperId(
$oxidation,
Ids::COPPER_DOOR,
Ids::EXPOSED_COPPER_DOOR,
Ids::WEATHERED_COPPER_DOOR,
Ids::OXIDIZED_COPPER_DOOR
)
)
);
});
$this->map(Blocks::COPPER_TRAPDOOR(), function(CopperTrapdoor $block) : Writer{
$oxidation = $block->getOxidation();
return Helper::encodeTrapdoor(
$block,
new Writer($block->isWaxed() ?
Helper::selectCopperId(
$oxidation,
Ids::WAXED_COPPER_TRAPDOOR,
Ids::WAXED_EXPOSED_COPPER_TRAPDOOR,
Ids::WAXED_WEATHERED_COPPER_TRAPDOOR,
Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR
) :
Helper::selectCopperId(
$oxidation,
Ids::COPPER_TRAPDOOR,
Ids::EXPOSED_COPPER_TRAPDOOR,
Ids::WEATHERED_COPPER_TRAPDOOR,
Ids::OXIDIZED_COPPER_TRAPDOOR
)
)
);
});
}
private function registerSimpleSerializers() : void{
$this->mapSimple(Blocks::AIR(), Ids::AIR);
$this->mapSimple(Blocks::AMETHYST(), Ids::AMETHYST_BLOCK);
@ -1265,175 +1438,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->mapSlab(Blocks::COBBLESTONE_SLAB(), Ids::COBBLESTONE_SLAB, Ids::COBBLESTONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::COBBLESTONE_STAIRS(), Ids::STONE_STAIRS);
$this->map(Blocks::COBBLESTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, Writer::create(Ids::COBBLESTONE_WALL)));
$this->map(Blocks::COPPER(), function(Copper $block) : Writer{
$oxidation = $block->getOxidation();
return new Writer($block->isWaxed() ?
Helper::selectCopperId($oxidation, Ids::WAXED_COPPER, Ids::WAXED_EXPOSED_COPPER, Ids::WAXED_WEATHERED_COPPER, Ids::WAXED_OXIDIZED_COPPER) :
Helper::selectCopperId($oxidation, Ids::COPPER_BLOCK, Ids::EXPOSED_COPPER, Ids::WEATHERED_COPPER, Ids::OXIDIZED_COPPER)
);
});
$this->map(Blocks::CHISELED_COPPER(), function(Copper $block) : Writer{
$oxidation = $block->getOxidation();
return new Writer($block->isWaxed() ?
Helper::selectCopperId($oxidation,
Ids::WAXED_CHISELED_COPPER,
Ids::WAXED_EXPOSED_CHISELED_COPPER,
Ids::WAXED_WEATHERED_CHISELED_COPPER,
Ids::WAXED_OXIDIZED_CHISELED_COPPER
) :
Helper::selectCopperId($oxidation,
Ids::CHISELED_COPPER,
Ids::EXPOSED_CHISELED_COPPER,
Ids::WEATHERED_CHISELED_COPPER,
Ids::OXIDIZED_CHISELED_COPPER
)
);
});
$this->map(Blocks::COPPER_GRATE(), function(CopperGrate $block) : Writer{
$oxidation = $block->getOxidation();
return new Writer($block->isWaxed() ?
Helper::selectCopperId($oxidation,
Ids::WAXED_COPPER_GRATE,
Ids::WAXED_EXPOSED_COPPER_GRATE,
Ids::WAXED_WEATHERED_COPPER_GRATE,
Ids::WAXED_OXIDIZED_COPPER_GRATE
) :
Helper::selectCopperId($oxidation,
Ids::COPPER_GRATE,
Ids::EXPOSED_COPPER_GRATE,
Ids::WEATHERED_COPPER_GRATE,
Ids::OXIDIZED_COPPER_GRATE
)
);
});
$this->map(Blocks::CUT_COPPER(), function(Copper $block) : Writer{
$oxidation = $block->getOxidation();
return new Writer($block->isWaxed() ?
Helper::selectCopperId($oxidation, Ids::WAXED_CUT_COPPER, Ids::WAXED_EXPOSED_CUT_COPPER, Ids::WAXED_WEATHERED_CUT_COPPER, Ids::WAXED_OXIDIZED_CUT_COPPER) :
Helper::selectCopperId($oxidation, Ids::CUT_COPPER, Ids::EXPOSED_CUT_COPPER, Ids::WEATHERED_CUT_COPPER, Ids::OXIDIZED_CUT_COPPER)
);
});
$this->map(Blocks::CUT_COPPER_SLAB(), function(CopperSlab $block) : Writer{
$oxidation = $block->getOxidation();
return Helper::encodeSlab(
$block,
($block->isWaxed() ?
Helper::selectCopperId(
$oxidation,
Ids::WAXED_CUT_COPPER_SLAB,
Ids::WAXED_EXPOSED_CUT_COPPER_SLAB,
Ids::WAXED_WEATHERED_CUT_COPPER_SLAB,
Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB
) :
Helper::selectCopperId(
$oxidation,
Ids::CUT_COPPER_SLAB,
Ids::EXPOSED_CUT_COPPER_SLAB,
Ids::WEATHERED_CUT_COPPER_SLAB,
Ids::OXIDIZED_CUT_COPPER_SLAB
)
),
($block->isWaxed() ?
Helper::selectCopperId(
$oxidation,
Ids::WAXED_DOUBLE_CUT_COPPER_SLAB,
Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB,
Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB,
Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB
) :
Helper::selectCopperId(
$oxidation,
Ids::DOUBLE_CUT_COPPER_SLAB,
Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB,
Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB,
Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB
)
)
);
});
$this->map(Blocks::CUT_COPPER_STAIRS(), function(CopperStairs $block) : Writer{
$oxidation = $block->getOxidation();
return Helper::encodeStairs(
$block,
new Writer($block->isWaxed() ?
Helper::selectCopperId(
$oxidation,
Ids::WAXED_CUT_COPPER_STAIRS,
Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS,
Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS,
Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS
) :
Helper::selectCopperId(
$oxidation,
Ids::CUT_COPPER_STAIRS,
Ids::EXPOSED_CUT_COPPER_STAIRS,
Ids::WEATHERED_CUT_COPPER_STAIRS,
Ids::OXIDIZED_CUT_COPPER_STAIRS
)
)
);
});
$this->map(Blocks::COPPER_BULB(), function(CopperBulb $block) : Writer{
$oxidation = $block->getOxidation();
return Writer::create($block->isWaxed() ?
Helper::selectCopperId($oxidation,
Ids::WAXED_COPPER_BULB,
Ids::WAXED_EXPOSED_COPPER_BULB,
Ids::WAXED_WEATHERED_COPPER_BULB,
Ids::WAXED_OXIDIZED_COPPER_BULB) :
Helper::selectCopperId($oxidation,
Ids::COPPER_BULB,
Ids::EXPOSED_COPPER_BULB,
Ids::WEATHERED_COPPER_BULB,
Ids::OXIDIZED_COPPER_BULB
))
->writeBool(StateNames::LIT, $block->isLit())
->writeBool(StateNames::POWERED_BIT, $block->isPowered());
});
$this->map(Blocks::COPPER_DOOR(), function(CopperDoor $block) : Writer{
$oxidation = $block->getOxidation();
return Helper::encodeDoor(
$block,
new Writer($block->isWaxed() ?
Helper::selectCopperId(
$oxidation,
Ids::WAXED_COPPER_DOOR,
Ids::WAXED_EXPOSED_COPPER_DOOR,
Ids::WAXED_WEATHERED_COPPER_DOOR,
Ids::WAXED_OXIDIZED_COPPER_DOOR
) :
Helper::selectCopperId(
$oxidation,
Ids::COPPER_DOOR,
Ids::EXPOSED_COPPER_DOOR,
Ids::WEATHERED_COPPER_DOOR,
Ids::OXIDIZED_COPPER_DOOR
)
)
);
});
$this->map(Blocks::COPPER_TRAPDOOR(), function(CopperTrapdoor $block) : Writer{
$oxidation = $block->getOxidation();
return Helper::encodeTrapdoor(
$block,
new Writer($block->isWaxed() ?
Helper::selectCopperId(
$oxidation,
Ids::WAXED_COPPER_TRAPDOOR,
Ids::WAXED_EXPOSED_COPPER_TRAPDOOR,
Ids::WAXED_WEATHERED_COPPER_TRAPDOOR,
Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR
) :
Helper::selectCopperId(
$oxidation,
Ids::COPPER_TRAPDOOR,
Ids::EXPOSED_COPPER_TRAPDOOR,
Ids::WEATHERED_COPPER_TRAPDOOR,
Ids::OXIDIZED_COPPER_TRAPDOOR
)
)
);
});
$this->map(Blocks::COCOA_POD(), function(CocoaBlock $block) : Writer{
return Writer::create(Ids::COCOA)
->writeInt(StateNames::AGE, $block->getAge())

View File

@ -126,12 +126,19 @@ final class BlockStateDeserializerHelper{
->setOutputSignalStrength($in->readBoundedInt(BlockStateNames::REDSTONE_SIGNAL, 0, 15));
}
/** @throws BlockStateDeserializeException */
/**
* @phpstan-template TDoor of Door
* @phpstan-param TDoor $block
* @phpstan-return TDoor
*
* @throws BlockStateDeserializeException
*/
public static function decodeDoor(Door $block, BlockStateReader $in) : Door{
//TODO: check if these need any special treatment to get the appropriate data to both halves of the door
return $block
->setTop($in->readBool(BlockStateNames::UPPER_BLOCK_BIT))
->setFacing($in->readCardinalHorizontalFacing())
//a door facing "east" is actually facing north - thanks mojang
->setFacing(Facing::rotateY($in->readCardinalHorizontalFacing(), clockwise: false))
->setHingeRight($in->readBool(BlockStateNames::DOOR_HINGE_BIT))
->setOpen($in->readBool(BlockStateNames::OPEN_BIT));
}
@ -236,18 +243,36 @@ final class BlockStateDeserializerHelper{
return $block->setPressed($in->readBoundedInt(BlockStateNames::REDSTONE_SIGNAL, 0, 15) !== 0);
}
/** @throws BlockStateDeserializeException */
/**
* @phpstan-template TSlab of Slab
* @phpstan-param TSlab $block
* @phpstan-return TSlab
*
* @throws BlockStateDeserializeException
*/
public static function decodeSingleSlab(Slab $block, BlockStateReader $in) : Slab{
return $block->setSlabType($in->readSlabPosition());
}
/** @throws BlockStateDeserializeException */
/**
* @phpstan-template TSlab of Slab
* @phpstan-param TSlab $block
* @phpstan-return TSlab
*
* @throws BlockStateDeserializeException
*/
public static function decodeDoubleSlab(Slab $block, BlockStateReader $in) : Slab{
$in->ignored(StateNames::MC_VERTICAL_HALF);
return $block->setSlabType(SlabType::DOUBLE);
}
/** @throws BlockStateDeserializeException */
/**
* @phpstan-template TStair of Stair
* @phpstan-param TStair $block
* @phpstan-return TStair
*
* @throws BlockStateDeserializeException
*/
public static function decodeStairs(Stair $block, BlockStateReader $in) : Stair{
return $block
->setUpsideDown($in->readBool(BlockStateNames::UPSIDE_DOWN_BIT))
@ -264,7 +289,13 @@ final class BlockStateDeserializerHelper{
->setFacing($facing === Facing::DOWN ? Facing::UP : $facing);
}
/** @throws BlockStateDeserializeException */
/**
* @phpstan-template TTrapdoor of Trapdoor
* @phpstan-param TTrapdoor $block
* @phpstan-return TTrapdoor
*
* @throws BlockStateDeserializeException
*/
public static function decodeTrapdoor(Trapdoor $block, BlockStateReader $in) : Trapdoor{
return $block
->setFacing($in->read5MinusHorizontalFacing())

View File

@ -100,7 +100,8 @@ final class BlockStateSerializerHelper{
public static function encodeDoor(Door $block, Writer $out) : Writer{
return $out
->writeBool(BlockStateNames::UPPER_BLOCK_BIT, $block->isTop())
->writeCardinalHorizontalFacing($block->getFacing())
//a door facing north is encoded as "east"
->writeCardinalHorizontalFacing(Facing::rotateY($block->getFacing(), clockwise: true))
->writeBool(BlockStateNames::DOOR_HINGE_BIT, $block->isHingeRight())
->writeBool(BlockStateNames::OPEN_BIT, $block->isOpen());
}

View File

@ -33,11 +33,13 @@ use pocketmine\block\DoublePitcherCrop;
use pocketmine\block\Opaque;
use pocketmine\block\PinkPetals;
use pocketmine\block\PitcherCrop;
use pocketmine\block\RuntimeBlockStateRegistry;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
use pocketmine\block\SweetBerryBush;
use pocketmine\block\utils\BrewingStandSlot;
use pocketmine\block\utils\ChiseledBookshelfSlot;
use pocketmine\block\utils\CopperMaterial;
use pocketmine\block\utils\CopperOxidation;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\DirtType;
@ -59,6 +61,7 @@ use pocketmine\data\bedrock\block\convert\BlockStateDeserializerHelper as Helper
use pocketmine\data\bedrock\block\convert\BlockStateReader as Reader;
use pocketmine\math\Axis;
use pocketmine\math\Facing;
use pocketmine\utils\Utils;
use function array_key_exists;
use function count;
use function min;
@ -87,6 +90,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->registerSaplingDeserializers();
$this->registerLightDeserializers();
$this->registerMobHeadDeserializers();
$this->registerCopperDeserializers();
$this->registerSimpleDeserializers();
$this->registerDeserializers();
}
@ -94,19 +98,37 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
public function deserialize(BlockStateData $stateData) : int{
if(count($stateData->getStates()) === 0){
//if a block has zero properties, we can keep a map of string ID -> internal blockstate ID
return $this->simpleCache[$stateData->getName()] ??= $this->deserializeBlock($stateData)->getStateId();
return $this->simpleCache[$stateData->getName()] ??= $this->deserializeToStateId($stateData);
}
//we can't cache blocks that have properties - go ahead and deserialize the slow way
return $this->deserializeBlock($stateData)->getStateId();
return $this->deserializeToStateId($stateData);
}
private function deserializeToStateId(BlockStateData $stateData) : int{
$stateId = $this->deserializeBlock($stateData)->getStateId();
//plugin devs seem to keep missing this and causing core crashes, so we need to verify this at the earliest
//available opportunity
if(!RuntimeBlockStateRegistry::getInstance()->hasStateId($stateId)){
throw new \LogicException("State ID $stateId returned by deserializer for " . $stateData->getName() . " is not registered in RuntimeBlockStateRegistry");
}
return $stateId;
}
/** @phpstan-param \Closure(Reader) : Block $c */
public function map(string $id, \Closure $c) : void{
if(array_key_exists($id, $this->deserializeFuncs)){
throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\"");
}
$this->deserializeFuncs[$id] = $c;
$this->simpleCache = [];
}
/**
* Returns the existing data deserializer for the given ID, or null if none exists.
* This may be useful if you need to override a deserializer, but still want to be able to fall back to the original.
*
* @phpstan-return ?\Closure(Reader) : Block
*/
public function getDeserializerForId(string $id) : ?\Closure{
return $this->deserializeFuncs[$id] ?? null;
}
/** @phpstan-param \Closure() : Block $getBlock */
@ -715,6 +737,150 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
}
}
/**
* @phpstan-param \Closure(Reader) : (CopperMaterial&Block) $deserializer
*/
private function mapCopper(
string $normalId,
string $waxedNormalId,
string $exposedId,
string $waxedExposedId,
string $weatheredId,
string $waxedWeatheredId,
string $oxidizedId,
string $waxedOxidizedId,
\Closure $deserializer
) : void{
foreach(Utils::stringifyKeys([
$normalId => [CopperOxidation::NONE, false],
$waxedNormalId => [CopperOxidation::NONE, true],
$exposedId => [CopperOxidation::EXPOSED, false],
$waxedExposedId => [CopperOxidation::EXPOSED, true],
$weatheredId => [CopperOxidation::WEATHERED, false],
$waxedWeatheredId => [CopperOxidation::WEATHERED, true],
$oxidizedId => [CopperOxidation::OXIDIZED, false],
$waxedOxidizedId => [CopperOxidation::OXIDIZED, true],
]) as $id => [$oxidation, $waxed]){
$this->map($id, fn(Reader $in) => $deserializer($in)->setOxidation($oxidation)->setWaxed($waxed));
}
}
private function registerCopperDeserializers() : void{
$this->mapCopper(
Ids::CUT_COPPER_SLAB,
Ids::WAXED_CUT_COPPER_SLAB,
Ids::EXPOSED_CUT_COPPER_SLAB,
Ids::WAXED_EXPOSED_CUT_COPPER_SLAB,
Ids::WEATHERED_CUT_COPPER_SLAB,
Ids::WAXED_WEATHERED_CUT_COPPER_SLAB,
Ids::OXIDIZED_CUT_COPPER_SLAB,
Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB,
fn(Reader $in) => Helper::decodeSingleSlab(Blocks::CUT_COPPER_SLAB(), $in)
);
$this->mapCopper(
Ids::DOUBLE_CUT_COPPER_SLAB,
Ids::WAXED_DOUBLE_CUT_COPPER_SLAB,
Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB,
Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB,
Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB,
Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB,
Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB,
Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB,
fn(Reader $in) => Helper::decodeDoubleSlab(Blocks::CUT_COPPER_SLAB(), $in)
);
$this->mapCopper(
Ids::COPPER_BULB,
Ids::WAXED_COPPER_BULB,
Ids::EXPOSED_COPPER_BULB,
Ids::WAXED_EXPOSED_COPPER_BULB,
Ids::WEATHERED_COPPER_BULB,
Ids::WAXED_WEATHERED_COPPER_BULB,
Ids::OXIDIZED_COPPER_BULB,
Ids::WAXED_OXIDIZED_COPPER_BULB,
fn(Reader $in) => Blocks::COPPER_BULB()
->setLit($in->readBool(StateNames::LIT))
->setPowered($in->readBool(StateNames::POWERED_BIT))
);
$this->mapCopper(
Ids::COPPER_DOOR,
Ids::WAXED_COPPER_DOOR,
Ids::EXPOSED_COPPER_DOOR,
Ids::WAXED_EXPOSED_COPPER_DOOR,
Ids::WEATHERED_COPPER_DOOR,
Ids::WAXED_WEATHERED_COPPER_DOOR,
Ids::OXIDIZED_COPPER_DOOR,
Ids::WAXED_OXIDIZED_COPPER_DOOR,
fn(Reader $in) => Helper::decodeDoor(Blocks::COPPER_DOOR(), $in)
);
$this->mapCopper(
Ids::COPPER_TRAPDOOR,
Ids::WAXED_COPPER_TRAPDOOR,
Ids::EXPOSED_COPPER_TRAPDOOR,
Ids::WAXED_EXPOSED_COPPER_TRAPDOOR,
Ids::WEATHERED_COPPER_TRAPDOOR,
Ids::WAXED_WEATHERED_COPPER_TRAPDOOR,
Ids::OXIDIZED_COPPER_TRAPDOOR,
Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR,
fn(Reader $in) => Helper::decodeTrapdoor(Blocks::COPPER_TRAPDOOR(), $in)
);
$this->mapCopper(
Ids::COPPER_BLOCK,
Ids::WAXED_COPPER,
Ids::EXPOSED_COPPER,
Ids::WAXED_EXPOSED_COPPER,
Ids::WEATHERED_COPPER,
Ids::WAXED_WEATHERED_COPPER,
Ids::OXIDIZED_COPPER,
Ids::WAXED_OXIDIZED_COPPER,
fn(Reader $in) => Blocks::COPPER()
);
$this->mapCopper(
Ids::CHISELED_COPPER,
Ids::WAXED_CHISELED_COPPER,
Ids::EXPOSED_CHISELED_COPPER,
Ids::WAXED_EXPOSED_CHISELED_COPPER,
Ids::WEATHERED_CHISELED_COPPER,
Ids::WAXED_WEATHERED_CHISELED_COPPER,
Ids::OXIDIZED_CHISELED_COPPER,
Ids::WAXED_OXIDIZED_CHISELED_COPPER,
fn(Reader $in) => Blocks::CHISELED_COPPER()
);
$this->mapCopper(
Ids::COPPER_GRATE,
Ids::WAXED_COPPER_GRATE,
Ids::EXPOSED_COPPER_GRATE,
Ids::WAXED_EXPOSED_COPPER_GRATE,
Ids::WEATHERED_COPPER_GRATE,
Ids::WAXED_WEATHERED_COPPER_GRATE,
Ids::OXIDIZED_COPPER_GRATE,
Ids::WAXED_OXIDIZED_COPPER_GRATE,
fn(Reader $in) => Blocks::COPPER_GRATE()
);
$this->mapCopper(
Ids::CUT_COPPER,
Ids::WAXED_CUT_COPPER,
Ids::EXPOSED_CUT_COPPER,
Ids::WAXED_EXPOSED_CUT_COPPER,
Ids::WEATHERED_CUT_COPPER,
Ids::WAXED_WEATHERED_CUT_COPPER,
Ids::OXIDIZED_CUT_COPPER,
Ids::WAXED_OXIDIZED_CUT_COPPER,
fn(Reader $in) => Blocks::CUT_COPPER()
);
$this->mapCopper(
Ids::CUT_COPPER_STAIRS,
Ids::WAXED_CUT_COPPER_STAIRS,
Ids::EXPOSED_CUT_COPPER_STAIRS,
Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS,
Ids::WEATHERED_CUT_COPPER_STAIRS,
Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS,
Ids::OXIDIZED_CUT_COPPER_STAIRS,
Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS,
fn(Reader $in) => Helper::decodeStairs(Blocks::CUT_COPPER_STAIRS(), $in)
);
}
private function registerSimpleDeserializers() : void{
$this->mapSimple(Ids::AIR, fn() => Blocks::AIR());
$this->mapSimple(Ids::AMETHYST_BLOCK, fn() => Blocks::AMETHYST());
@ -1220,18 +1386,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->map(Ids::COMPOUND_CREATOR, fn(Reader $in) => Blocks::COMPOUND_CREATOR()
->setFacing(Facing::opposite($in->readLegacyHorizontalFacing()))
);
$this->map(Ids::COPPER_BLOCK, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::NONE));
$this->map(Ids::COPPER_BULB, function(Reader $in) : Block{
return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE)
->setLit($in->readBool(StateNames::LIT))
->setPowered($in->readBool(StateNames::POWERED_BIT));
});
$this->map(Ids::COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in));
$this->map(Ids::COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE));
$this->map(Ids::COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in));
$this->map(Ids::CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE));
$this->mapSlab(Ids::CUT_COPPER_SLAB, Ids::DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE));
$this->mapStairs(Ids::CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE));
$this->mapSlab(Ids::CUT_RED_SANDSTONE_SLAB, Ids::CUT_RED_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::CUT_RED_SANDSTONE_SLAB());
$this->mapSlab(Ids::CUT_SANDSTONE_SLAB, Ids::CUT_SANDSTONE_DOUBLE_SLAB, fn() => Blocks::CUT_SANDSTONE_SLAB());
$this->mapSlab(Ids::DARK_PRISMARINE_SLAB, Ids::DARK_PRISMARINE_DOUBLE_SLAB, fn() => Blocks::DARK_PRISMARINE_SLAB());
@ -1286,19 +1440,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
return Blocks::ENDER_CHEST()
->setFacing($in->readCardinalHorizontalFacing());
});
$this->map(Ids::EXPOSED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::EXPOSED));
$this->map(Ids::EXPOSED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED));
$this->map(Ids::EXPOSED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED));
$this->map(Ids::EXPOSED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED));
$this->mapSlab(Ids::EXPOSED_CUT_COPPER_SLAB, Ids::EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED));
$this->mapStairs(Ids::EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED));
$this->map(Ids::EXPOSED_COPPER_BULB, function(Reader $in) : Block{
return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED)
->setLit($in->readBool(StateNames::LIT))
->setPowered($in->readBool(StateNames::POWERED_BIT));
});
$this->map(Ids::EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in));
$this->map(Ids::EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in));
$this->map(Ids::FARMLAND, function(Reader $in) : Block{
return Blocks::FARMLAND()
->setWetness($in->readBoundedInt(StateNames::MOISTURIZED_AMOUNT, 0, 7));
@ -1451,19 +1592,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->mapSlab(Ids::NORMAL_STONE_SLAB, Ids::NORMAL_STONE_DOUBLE_SLAB, fn() => Blocks::STONE_SLAB());
$this->mapStairs(Ids::NORMAL_STONE_STAIRS, fn() => Blocks::STONE_STAIRS());
$this->map(Ids::OCHRE_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::OCHRE)->setAxis($in->readPillarAxis()));
$this->map(Ids::OXIDIZED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED));
$this->map(Ids::OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED));
$this->map(Ids::OXIDIZED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED));
$this->map(Ids::OXIDIZED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED));
$this->mapSlab(Ids::OXIDIZED_CUT_COPPER_SLAB, Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED));
$this->mapStairs(Ids::OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED));
$this->map(Ids::OXIDIZED_COPPER_BULB, function(Reader $in) : Block{
return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED)
->setLit($in->readBool(StateNames::LIT))
->setPowered($in->readBool(StateNames::POWERED_BIT));
});
$this->map(Ids::OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in));
$this->map(Ids::OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in));
$this->map(Ids::PEARLESCENT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::PEARLESCENT)->setAxis($in->readPillarAxis()));
$this->mapSlab(Ids::PETRIFIED_OAK_SLAB, Ids::PETRIFIED_OAK_DOUBLE_SLAB, fn() => Blocks::FAKE_WOODEN_SLAB());
$this->map(Ids::PINK_PETALS, function(Reader $in) : Block{
@ -1736,71 +1864,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
->setFacing($in->readHorizontalFacing());
});
$this->map(Ids::WATER, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::WATER(), $in));
$this->map(Ids::WAXED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::NONE));
$this->map(Ids::WAXED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::NONE));
$this->map(Ids::WAXED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::NONE));
$this->map(Ids::WAXED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE));
$this->mapSlab(Ids::WAXED_CUT_COPPER_SLAB, Ids::WAXED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::NONE));
$this->mapStairs(Ids::WAXED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::NONE));
$this->map(Ids::WAXED_COPPER_BULB, function(Reader $in) : Block{
return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::NONE)
->setLit($in->readBool(StateNames::LIT))
->setPowered($in->readBool(StateNames::POWERED_BIT));
});
$this->map(Ids::WAXED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::NONE), $in));
$this->map(Ids::WAXED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::NONE), $in));
$this->map(Ids::WAXED_EXPOSED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::EXPOSED));
$this->map(Ids::WAXED_EXPOSED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::EXPOSED));
$this->map(Ids::WAXED_EXPOSED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::EXPOSED));
$this->map(Ids::WAXED_EXPOSED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::EXPOSED));
$this->mapSlab(Ids::WAXED_EXPOSED_CUT_COPPER_SLAB, Ids::WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::EXPOSED));
$this->mapStairs(Ids::WAXED_EXPOSED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::EXPOSED));
$this->map(Ids::WAXED_EXPOSED_COPPER_BULB, function(Reader $in) : Block{
return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::EXPOSED)
->setLit($in->readBool(StateNames::LIT))
->setPowered($in->readBool(StateNames::POWERED_BIT));
});
$this->map(Ids::WAXED_EXPOSED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::EXPOSED), $in));
$this->map(Ids::WAXED_EXPOSED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::EXPOSED), $in));
$this->map(Ids::WAXED_OXIDIZED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED));
$this->map(Ids::WAXED_OXIDIZED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::OXIDIZED));
$this->map(Ids::WAXED_OXIDIZED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::OXIDIZED));
$this->map(Ids::WAXED_OXIDIZED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED));
$this->mapSlab(Ids::WAXED_OXIDIZED_CUT_COPPER_SLAB, Ids::WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED));
$this->mapStairs(Ids::WAXED_OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED));
$this->map(Ids::WAXED_OXIDIZED_COPPER_BULB, function(Reader $in) : Block{
return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::OXIDIZED)
->setLit($in->readBool(StateNames::LIT))
->setPowered($in->readBool(StateNames::POWERED_BIT));
});
$this->map(Ids::WAXED_OXIDIZED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::OXIDIZED), $in));
$this->map(Ids::WAXED_OXIDIZED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::OXIDIZED), $in));
$this->map(Ids::WAXED_WEATHERED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::WEATHERED));
$this->map(Ids::WAXED_WEATHERED_CHISELED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED));
$this->map(Ids::WAXED_WEATHERED_COPPER_GRATE, fn() => Helper::decodeWaxedCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED));
$this->map(Ids::WAXED_WEATHERED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED));
$this->mapSlab(Ids::WAXED_WEATHERED_CUT_COPPER_SLAB, Ids::WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED));
$this->mapStairs(Ids::WAXED_WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED));
$this->map(Ids::WAXED_WEATHERED_COPPER_BULB, function(Reader $in) : Block{
return Helper::decodeWaxedCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED)
->setLit($in->readBool(StateNames::LIT))
->setPowered($in->readBool(StateNames::POWERED_BIT));
});
$this->map(Ids::WAXED_WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeWaxedCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in));
$this->map(Ids::WAXED_WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeWaxedCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in));
$this->map(Ids::WEATHERED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::WEATHERED));
$this->map(Ids::WEATHERED_CHISELED_COPPER, fn() => Helper::decodeCopper(Blocks::CHISELED_COPPER(), CopperOxidation::WEATHERED));
$this->map(Ids::WEATHERED_COPPER_GRATE, fn() => Helper::decodeCopper(Blocks::COPPER_GRATE(), CopperOxidation::WEATHERED));
$this->map(Ids::WEATHERED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::WEATHERED));
$this->mapSlab(Ids::WEATHERED_CUT_COPPER_SLAB, Ids::WEATHERED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::WEATHERED));
$this->mapStairs(Ids::WEATHERED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::WEATHERED));
$this->map(Ids::WEATHERED_COPPER_BULB, function(Reader $in) : Block{
return Helper::decodeCopper(Blocks::COPPER_BULB(), CopperOxidation::WEATHERED)
->setLit($in->readBool(StateNames::LIT))
->setPowered($in->readBool(StateNames::POWERED_BIT));
});
$this->map(Ids::WEATHERED_COPPER_DOOR, fn(Reader $in) => Helper::decodeDoor(Helper::decodeCopper(Blocks::COPPER_DOOR(), CopperOxidation::WEATHERED), $in));
$this->map(Ids::WEATHERED_COPPER_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Helper::decodeCopper(Blocks::COPPER_TRAPDOOR(), CopperOxidation::WEATHERED), $in));
$this->map(Ids::WEEPING_VINES, function(Reader $in) : Block{
return Blocks::WEEPING_VINES()
->setAge($in->readBoundedInt(StateNames::WEEPING_VINES_AGE, 0, 25));

View File

@ -51,12 +51,19 @@ final class ItemDeserializer{
* @phpstan-param \Closure(Data) : Item $deserializer
*/
public function map(string $id, \Closure $deserializer) : void{
if(isset($this->deserializers[$id])){
throw new \InvalidArgumentException("Deserializer is already assigned for \"$id\"");
}
$this->deserializers[$id] = $deserializer;
}
/**
* Returns the existing data deserializer for the given ID, or null if none exists.
* This may be useful if you need to override a deserializer, but still want to be able to fall back to the original.
*
* @phpstan-return ?\Closure(Data) : Item
*/
public function getDeserializerForId(string $id) : ?\Closure{
return $this->deserializers[$id] ?? null;
}
/**
* @phpstan-param \Closure(Data) : Block $deserializer
*/

View File

@ -68,6 +68,7 @@ final class ItemTypeNames{
public const BIRCH_SIGN = "minecraft:birch_sign";
public const BLACK_BUNDLE = "minecraft:black_bundle";
public const BLACK_DYE = "minecraft:black_dye";
public const BLACK_HARNESS = "minecraft:black_harness";
public const BLADE_POTTERY_SHERD = "minecraft:blade_pottery_sherd";
public const BLAZE_POWDER = "minecraft:blaze_powder";
public const BLAZE_ROD = "minecraft:blaze_rod";
@ -75,6 +76,8 @@ final class ItemTypeNames{
public const BLEACH = "minecraft:bleach";
public const BLUE_BUNDLE = "minecraft:blue_bundle";
public const BLUE_DYE = "minecraft:blue_dye";
public const BLUE_EGG = "minecraft:blue_egg";
public const BLUE_HARNESS = "minecraft:blue_harness";
public const BOARD = "minecraft:board";
public const BOAT = "minecraft:boat";
public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg";
@ -93,6 +96,8 @@ final class ItemTypeNames{
public const BRICK = "minecraft:brick";
public const BROWN_BUNDLE = "minecraft:brown_bundle";
public const BROWN_DYE = "minecraft:brown_dye";
public const BROWN_EGG = "minecraft:brown_egg";
public const BROWN_HARNESS = "minecraft:brown_harness";
public const BRUSH = "minecraft:brush";
public const BUCKET = "minecraft:bucket";
public const BUNDLE = "minecraft:bundle";
@ -164,6 +169,7 @@ final class ItemTypeNames{
public const CROSSBOW = "minecraft:crossbow";
public const CYAN_BUNDLE = "minecraft:cyan_bundle";
public const CYAN_DYE = "minecraft:cyan_dye";
public const CYAN_HARNESS = "minecraft:cyan_harness";
public const DANGER_POTTERY_SHERD = "minecraft:danger_pottery_sherd";
public const DARK_OAK_BOAT = "minecraft:dark_oak_boat";
public const DARK_OAK_CHEST_BOAT = "minecraft:dark_oak_chest_boat";
@ -263,12 +269,15 @@ final class ItemTypeNames{
public const GOLDEN_SWORD = "minecraft:golden_sword";
public const GRAY_BUNDLE = "minecraft:gray_bundle";
public const GRAY_DYE = "minecraft:gray_dye";
public const GRAY_HARNESS = "minecraft:gray_harness";
public const GREEN_BUNDLE = "minecraft:green_bundle";
public const GREEN_DYE = "minecraft:green_dye";
public const GREEN_HARNESS = "minecraft:green_harness";
public const GUARDIAN_SPAWN_EGG = "minecraft:guardian_spawn_egg";
public const GUNPOWDER = "minecraft:gunpowder";
public const GUSTER_BANNER_PATTERN = "minecraft:guster_banner_pattern";
public const GUSTER_POTTERY_SHERD = "minecraft:guster_pottery_sherd";
public const HAPPY_GHAST_SPAWN_EGG = "minecraft:happy_ghast_spawn_egg";
public const HARD_STAINED_GLASS = "minecraft:hard_stained_glass";
public const HARD_STAINED_GLASS_PANE = "minecraft:hard_stained_glass_pane";
public const HEART_OF_THE_SEA = "minecraft:heart_of_the_sea";
@ -319,10 +328,13 @@ final class ItemTypeNames{
public const LIGHT_BLOCK = "minecraft:light_block";
public const LIGHT_BLUE_BUNDLE = "minecraft:light_blue_bundle";
public const LIGHT_BLUE_DYE = "minecraft:light_blue_dye";
public const LIGHT_BLUE_HARNESS = "minecraft:light_blue_harness";
public const LIGHT_GRAY_BUNDLE = "minecraft:light_gray_bundle";
public const LIGHT_GRAY_DYE = "minecraft:light_gray_dye";
public const LIGHT_GRAY_HARNESS = "minecraft:light_gray_harness";
public const LIME_BUNDLE = "minecraft:lime_bundle";
public const LIME_DYE = "minecraft:lime_dye";
public const LIME_HARNESS = "minecraft:lime_harness";
public const LINGERING_POTION = "minecraft:lingering_potion";
public const LLAMA_SPAWN_EGG = "minecraft:llama_spawn_egg";
public const LODESTONE_COMPASS = "minecraft:lodestone_compass";
@ -331,6 +343,7 @@ final class ItemTypeNames{
public const MACE = "minecraft:mace";
public const MAGENTA_BUNDLE = "minecraft:magenta_bundle";
public const MAGENTA_DYE = "minecraft:magenta_dye";
public const MAGENTA_HARNESS = "minecraft:magenta_harness";
public const MAGMA_CREAM = "minecraft:magma_cream";
public const MAGMA_CUBE_SPAWN_EGG = "minecraft:magma_cube_spawn_egg";
public const MANGROVE_BOAT = "minecraft:mangrove_boat";
@ -398,6 +411,7 @@ final class ItemTypeNames{
public const OMINOUS_TRIAL_KEY = "minecraft:ominous_trial_key";
public const ORANGE_BUNDLE = "minecraft:orange_bundle";
public const ORANGE_DYE = "minecraft:orange_dye";
public const ORANGE_HARNESS = "minecraft:orange_harness";
public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door";
public const PAINTING = "minecraft:painting";
public const PALE_OAK_BOAT = "minecraft:pale_oak_boat";
@ -417,6 +431,7 @@ final class ItemTypeNames{
public const PILLAGER_SPAWN_EGG = "minecraft:pillager_spawn_egg";
public const PINK_BUNDLE = "minecraft:pink_bundle";
public const PINK_DYE = "minecraft:pink_dye";
public const PINK_HARNESS = "minecraft:pink_harness";
public const PITCHER_POD = "minecraft:pitcher_pod";
public const PLANKS = "minecraft:planks";
public const PLENTY_POTTERY_SHERD = "minecraft:plenty_pottery_sherd";
@ -437,6 +452,7 @@ final class ItemTypeNames{
public const PUMPKIN_SEEDS = "minecraft:pumpkin_seeds";
public const PURPLE_BUNDLE = "minecraft:purple_bundle";
public const PURPLE_DYE = "minecraft:purple_dye";
public const PURPLE_HARNESS = "minecraft:purple_harness";
public const QUARTZ = "minecraft:quartz";
public const RABBIT = "minecraft:rabbit";
public const RABBIT_FOOT = "minecraft:rabbit_foot";
@ -453,6 +469,7 @@ final class ItemTypeNames{
public const RED_BUNDLE = "minecraft:red_bundle";
public const RED_DYE = "minecraft:red_dye";
public const RED_FLOWER = "minecraft:red_flower";
public const RED_HARNESS = "minecraft:red_harness";
public const REDSTONE = "minecraft:redstone";
public const REPEATER = "minecraft:repeater";
public const RESIN_BRICK = "minecraft:resin_brick";
@ -561,6 +578,7 @@ final class ItemTypeNames{
public const WHEAT_SEEDS = "minecraft:wheat_seeds";
public const WHITE_BUNDLE = "minecraft:white_bundle";
public const WHITE_DYE = "minecraft:white_dye";
public const WHITE_HARNESS = "minecraft:white_harness";
public const WILD_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:wild_armor_trim_smithing_template";
public const WIND_CHARGE = "minecraft:wind_charge";
public const WITCH_SPAWN_EGG = "minecraft:witch_spawn_egg";
@ -581,6 +599,7 @@ final class ItemTypeNames{
public const WRITTEN_BOOK = "minecraft:written_book";
public const YELLOW_BUNDLE = "minecraft:yellow_bundle";
public const YELLOW_DYE = "minecraft:yellow_dye";
public const YELLOW_HARNESS = "minecraft:yellow_harness";
public const ZOGLIN_SPAWN_EGG = "minecraft:zoglin_spawn_egg";
public const ZOMBIE_HORSE_SPAWN_EGG = "minecraft:zombie_horse_spawn_egg";
public const ZOMBIE_PIGMAN_SPAWN_EGG = "minecraft:zombie_pigman_spawn_egg";

View File

@ -60,6 +60,7 @@ use pocketmine\player\Player;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\Limits;
use pocketmine\utils\Utils;
use pocketmine\VersionInfo;
use pocketmine\world\format\Chunk;
@ -76,6 +77,7 @@ use function floatval;
use function floor;
use function fmod;
use function get_class;
use function min;
use function sin;
use function spl_object_id;
use const M_PI_2;
@ -700,9 +702,16 @@ abstract class Entity{
* @throws \InvalidArgumentException
*/
public function setFireTicks(int $fireTicks) : void{
if($fireTicks < 0 || $fireTicks > 0x7fff){
throw new \InvalidArgumentException("Fire ticks must be in range 0 ... " . 0x7fff . ", got $fireTicks");
if($fireTicks < 0){
throw new \InvalidArgumentException("Fire ticks cannot be negative");
}
//Since the max value is not externally obvious or intuitive, many plugins use this without being aware that
//reasonably large values are not accepted. We even have such usages within PM itself. It doesn't make sense
//to force all those calls to be aware of this limitation, as it's not a functional limit but a limitation of
//the Mojang save format. Truncating this to the max acceptable value is the next best thing we can do.
$fireTicks = min($fireTicks, Limits::INT16_MAX);
if(!$this->isFireProof()){
$this->fireTicks = $fireTicks;
$this->networkPropertiesDirty = true;
@ -1495,7 +1504,7 @@ abstract class Entity{
$this->getId(), //TODO: actor unique ID
$this->getId(),
static::getNetworkTypeId(),
$this->location->asVector3(),
$this->getOffsetPosition($this->location->asVector3()),
$this->getMotion(),
$this->location->pitch,
$this->location->yaw,

View File

@ -144,8 +144,9 @@ class InventoryTransaction{
$needItems = [];
$haveItems = [];
foreach($this->actions as $key => $action){
if(!$action->getTargetItem()->isNull()){
$needItems[] = $action->getTargetItem();
$targetItem = $action->getTargetItem();
if(!$targetItem->isNull()){
$needItems[] = $targetItem;
}
try{
@ -154,8 +155,9 @@ class InventoryTransaction{
throw new TransactionValidationException(get_class($action) . "#" . spl_object_id($action) . ": " . $e->getMessage(), 0, $e);
}
if(!$action->getSourceItem()->isNull()){
$haveItems[] = $action->getSourceItem();
$sourceItem = $action->getSourceItem();
if(!$sourceItem->isNull()){
$haveItems[] = $sourceItem;
}
}

View File

@ -111,7 +111,8 @@ final class LegacyStringToItemParser{
*/
public function parse(string $input) : Item{
$key = $this->reprocess($input);
$b = explode(":", $key);
//TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given a string like 351:4:1
$b = explode(":", $key, limit: 3);
if(!isset($b[1])){
$meta = 0;

View File

@ -28,6 +28,7 @@ use pocketmine\item\enchantment\ItemEnchantmentTags as Tags;
use pocketmine\item\enchantment\VanillaEnchantments as Enchantments;
use pocketmine\item\Item;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use function array_filter;
use function array_values;
use function count;
@ -129,6 +130,7 @@ final class AvailableEnchantmentRegistry{
if(!$this->isRegistered($enchantment)){
throw new \LogicException("Cannot set primary item tags for non-registered enchantment");
}
Utils::validateArrayValueType($tags, fn(string $v) => 1);
$this->primaryItemTags[spl_object_id($enchantment)] = array_values($tags);
}
@ -152,6 +154,7 @@ final class AvailableEnchantmentRegistry{
if(!$this->isRegistered($enchantment)){
throw new \LogicException("Cannot set secondary item tags for non-registered enchantment");
}
Utils::validateArrayValueType($tags, fn(string $v) => 1);
$this->secondaryItemTags[spl_object_id($enchantment)] = array_values($tags);
}

View File

@ -71,7 +71,7 @@ class Language{
foreach($files as $file){
try{
$code = explode(".", $file)[0];
$code = explode(".", $file, limit: 2)[0];
$strings = self::loadLang($path, $code);
if(isset($strings[KnownTranslationKeys::LANGUAGE_NAME])){
$result[$code] = $strings[KnownTranslationKeys::LANGUAGE_NAME];

View File

@ -72,9 +72,11 @@ final class JwtUtils{
* @throws JwtException
*/
public static function split(string $jwt) : array{
$v = explode(".", $jwt);
//limit of 4 allows us to detect too many parts without having to split the string up into a potentially large
//number of parts
$v = explode(".", $jwt, limit: 4);
if(count($v) !== 3){
throw new JwtException("Expected exactly 3 JWT parts, got " . count($v));
throw new JwtException("Expected exactly 3 JWT parts delimited by a period");
}
return [$v[0], $v[1], $v[2]]; //workaround phpstan bug
}

View File

@ -88,9 +88,13 @@ class ChunkCache implements ChunkListener{
private int $hits = 0;
private int $misses = 0;
/**
* @phpstan-param DimensionIds::* $dimensionId
*/
private function __construct(
private World $world,
private Compressor $compressor
private Compressor $compressor,
private int $dimensionId = DimensionIds::OVERWORLD
){}
private function prepareChunkAsync(int $chunkX, int $chunkZ, int $chunkHash) : CompressBatchPromise{
@ -109,7 +113,7 @@ class ChunkCache implements ChunkListener{
new ChunkRequestTask(
$chunkX,
$chunkZ,
DimensionIds::OVERWORLD, //TODO: not hardcode this
$this->dimensionId,
$chunk,
$promise,
$this->compressor

View File

@ -23,13 +23,22 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\cache;
use pocketmine\color\Color;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket;
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use pocketmine\network\mcpe\protocol\types\biome\BiomeDefinitionEntry;
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
use pocketmine\utils\Filesystem;
use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils;
use pocketmine\world\biome\model\BiomeDefinitionEntryData;
use function count;
use function get_debug_type;
use function is_array;
use function json_decode;
class StaticPacketCache{
use SingletonTrait;
@ -41,9 +50,61 @@ class StaticPacketCache{
return new CacheableNbt((new NetworkNbtSerializer())->read(Filesystem::fileGetContents($filePath))->mustGetCompoundTag());
}
/**
* @return list<BiomeDefinitionEntry>
*/
private static function loadBiomeDefinitionModel(string $filePath) : array{
$biomeEntries = json_decode(Filesystem::fileGetContents($filePath), associative: true);
if(!is_array($biomeEntries)){
throw new SavedDataLoadingException("$filePath root should be an array, got " . get_debug_type($biomeEntries));
}
$jsonMapper = new \JsonMapper();
$jsonMapper->bExceptionOnMissingData = true;
$jsonMapper->bStrictObjectTypeChecking = true;
$jsonMapper->bEnforceMapType = false;
$entries = [];
foreach(Utils::promoteKeys($biomeEntries) as $biomeName => $entry){
if(!is_array($entry)){
throw new SavedDataLoadingException("$filePath should be an array of objects, got " . get_debug_type($entry));
}
try{
$biomeDefinition = $jsonMapper->map($entry, new BiomeDefinitionEntryData());
$mapWaterColour = $biomeDefinition->mapWaterColour;
$entries[] = new BiomeDefinitionEntry(
(string) $biomeName,
$biomeDefinition->id,
$biomeDefinition->temperature,
$biomeDefinition->downfall,
$biomeDefinition->redSporeDensity,
$biomeDefinition->blueSporeDensity,
$biomeDefinition->ashDensity,
$biomeDefinition->whiteAshDensity,
$biomeDefinition->depth,
$biomeDefinition->scale,
new Color(
$mapWaterColour->r,
$mapWaterColour->g,
$mapWaterColour->b,
$mapWaterColour->a
),
$biomeDefinition->rain,
count($biomeDefinition->tags) > 0 ? $biomeDefinition->tags : null,
);
}catch(\JsonMapper_Exception $e){
throw new \RuntimeException($e->getMessage(), 0, $e);
}
}
return $entries;
}
private static function make() : self{
return new self(
BiomeDefinitionListPacket::create(self::loadCompoundFromFile(BedrockDataFiles::BIOME_DEFINITIONS_NBT)),
BiomeDefinitionListPacket::fromDefinitions(self::loadBiomeDefinitionModel(BedrockDataFiles::BIOME_DEFINITIONS_JSON)),
AvailableActorIdentifiersPacket::create(self::loadCompoundFromFile(BedrockDataFiles::ENTITY_IDENTIFIERS_NBT))
);
}

View File

@ -64,7 +64,6 @@ use pocketmine\network\mcpe\protocol\ItemStackResponsePacket;
use pocketmine\network\mcpe\protocol\LabTablePacket;
use pocketmine\network\mcpe\protocol\LecternUpdatePacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacket;
use pocketmine\network\mcpe\protocol\LevelSoundEventPacketV1;
use pocketmine\network\mcpe\protocol\MapInfoRequestPacket;
use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket;
use pocketmine\network\mcpe\protocol\MobEquipmentPacket;
@ -74,7 +73,6 @@ use pocketmine\network\mcpe\protocol\NetworkStackLatencyPacket;
use pocketmine\network\mcpe\protocol\PlayerActionPacket;
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket;
use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\serializer\BitSet;
@ -296,10 +294,6 @@ class InGamePacketHandler extends PacketHandler{
return $packetHandled;
}
public function handleLevelSoundEventPacketV1(LevelSoundEventPacketV1 $packet) : bool{
return true; //useless leftover from 1.8
}
public function handleActorEvent(ActorEventPacket $packet) : bool{
if($packet->actorRuntimeId !== $this->player->getId()){
//TODO HACK: EATING_ITEM is sent back to the server when the server sends it for other players (1.14 bug, maybe earlier)
@ -786,10 +780,6 @@ class InGamePacketHandler extends PacketHandler{
return false;
}
public function handlePlayerInput(PlayerInputPacket $packet) : bool{
return false; //TODO
}
public function handleSetPlayerGameType(SetPlayerGameTypePacket $packet) : bool{
$gameMode = $this->session->getTypeConverter()->protocolGameModeToCore($packet->gamemode);
if($gameMode !== $this->player->getGamemode()){

View File

@ -148,7 +148,9 @@ class BanEntry{
return null;
}
$parts = explode("|", trim($str));
//we expect at most 5 parts, but accept 6 in case of an extra unexpected delimiter
//we don't want to include unexpected data into the ban reason
$parts = explode("|", trim($str), limit: 6);
$entry = new BanEntry(trim(array_shift($parts)));
if(count($parts) > 0){
$entry->setCreated(self::parseDate(array_shift($parts)));

View File

@ -25,6 +25,8 @@ namespace pocketmine\player;
use pocketmine\block\Block;
use pocketmine\entity\animation\ArmSwingAnimation;
use pocketmine\entity\effect\VanillaEffects;
use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\network\mcpe\protocol\LevelEventPacket;
@ -65,11 +67,29 @@ final class SurvivalBlockBreakHandler{
if(!$this->block->getBreakInfo()->isBreakable()){
return 0.0;
}
//TODO: improve this to take stuff like swimming, ladders, enchanted tools into account, fix wrong tool break time calculations for bad tools (pmmp/PocketMine-MP#211)
$breakTimePerTick = $this->block->getBreakInfo()->getBreakTime($this->player->getInventory()->getItemInHand()) * 20;
if(!$this->player->isOnGround() && !$this->player->isFlying()){
$breakTimePerTick *= 5;
}
if($this->player->isUnderwater() && !$this->player->getArmorInventory()->getHelmet()->hasEnchantment(VanillaEnchantments::AQUA_AFFINITY())){
$breakTimePerTick *= 5;
}
if($breakTimePerTick > 0){
return 1 / $breakTimePerTick;
$progressPerTick = 1 / $breakTimePerTick;
$haste = $this->player->getEffects()->get(VanillaEffects::HASTE());
if($haste !== null){
$hasteLevel = $haste->getEffectLevel();
$progressPerTick *= (1 + 0.2 * $hasteLevel) * (1.2 ** $hasteLevel);
}
$miningFatigue = $this->player->getEffects()->get(VanillaEffects::MINING_FATIGUE());
if($miningFatigue !== null){
$miningFatigueLevel = $miningFatigue->getEffectLevel();
$progressPerTick *= 0.21 ** $miningFatigueLevel;
}
return $progressPerTick;
}
return 1;
}
@ -82,7 +102,10 @@ final class SurvivalBlockBreakHandler{
$newBreakSpeed = $this->calculateBreakProgressPerTick();
if(abs($newBreakSpeed - $this->breakSpeed) > 0.0001){
$this->breakSpeed = $newBreakSpeed;
//TODO: sync with client
$this->player->getWorld()->broadcastPacketToViewers(
$this->blockPos,
LevelEventPacket::create(LevelEvent::BLOCK_BREAK_SPEED, (int) (65535 * $this->breakSpeed), $this->blockPos)
);
}
$this->breakProgress += $this->breakSpeed;

View File

@ -26,6 +26,7 @@ namespace pocketmine\resourcepacks;
use pocketmine\utils\Config;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use Ramsey\Uuid\Uuid;
use Symfony\Component\Filesystem\Path;
use function array_keys;
use function copy;
@ -103,9 +104,14 @@ class ResourcePackManager{
try{
$newPack = $this->loadPackFromPath(Path::join($this->path, $pack));
$this->resourcePacks[] = $newPack;
$index = strtolower($newPack->getPackId());
if(!Uuid::isValid($index)){
//TODO: we should use Uuid in ResourcePack interface directly but that would break BC
//for now we need to validate this here to make sure it doesn't cause crashes later on
throw new ResourcePackException("Invalid UUID ($index)");
}
$this->uuidList[$index] = $newPack;
$this->resourcePacks[] = $newPack;
$keyPath = Path::join($this->path, $pack . ".key");
if(file_exists($keyPath)){
@ -190,6 +196,11 @@ class ResourcePackManager{
$resourcePacks = [];
foreach($resourceStack as $pack){
$uuid = strtolower($pack->getPackId());
if(!Uuid::isValid($uuid)){
//TODO: we should use Uuid in ResourcePack interface directly but that would break BC
//for now we need to validate this here to make sure it doesn't cause crashes later on
throw new \InvalidArgumentException("Invalid resource pack UUID ($uuid)");
}
if(isset($uuidList[$uuid])){
throw new \InvalidArgumentException("Cannot load two resource pack with the same UUID ($uuid)");
}

View File

@ -84,6 +84,6 @@ final class ThreadCrashInfo extends ThreadSafe{
public function getThreadName() : string{ return $this->threadName; }
public function makePrettyMessage() : string{
return sprintf("%s: \"%s\" in \"%s\" on line %d", $this->type ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line);
return sprintf("%s: \"%s\" in \"%s\" on line %d", $this->type, $this->message, Filesystem::cleanPath($this->file), $this->line);
}
}

View File

@ -54,6 +54,7 @@ use const CASE_LOWER;
use const JSON_BIGINT_AS_STRING;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
use const PHP_INT_MAX;
use const YAML_UTF8_ENCODING;
/**
@ -339,7 +340,7 @@ class Config{
}
public function setNested(string $key, mixed $value) : void{
$vars = explode(".", $key);
$vars = explode(".", $key, limit: PHP_INT_MAX);
$base = array_shift($vars);
if(!isset($this->config[$base])){
@ -366,7 +367,7 @@ class Config{
return $this->nestedCache[$key];
}
$vars = explode(".", $key);
$vars = explode(".", $key, limit: PHP_INT_MAX);
$base = array_shift($vars);
if(isset($this->config[$base])){
$base = $this->config[$base];
@ -390,7 +391,7 @@ class Config{
$this->nestedCache = [];
$this->changed = true;
$vars = explode(".", $key);
$vars = explode(".", $key, limit: PHP_INT_MAX);
$currentNode = &$this->config;
while(count($vars) > 0){
@ -495,7 +496,7 @@ class Config{
*/
public static function parseList(string $content) : array{
$result = [];
foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){
foreach(explode("\n", trim(str_replace("\r\n", "\n", $content)), limit: PHP_INT_MAX) as $v){
$v = trim($v);
if($v === ""){
continue;

View File

@ -60,6 +60,7 @@ use const CURLOPT_RETURNTRANSFER;
use const CURLOPT_SSL_VERIFYHOST;
use const CURLOPT_SSL_VERIFYPEER;
use const CURLOPT_TIMEOUT_MS;
use const PHP_INT_MAX;
use const SOCK_DGRAM;
use const SOL_UDP;
@ -227,9 +228,10 @@ class Internet{
$rawHeaders = substr($raw, 0, $headerSize);
$body = substr($raw, $headerSize);
$headers = [];
foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){
//TODO: explore if we can set these limits lower
foreach(explode("\r\n\r\n", $rawHeaders, limit: PHP_INT_MAX) as $rawHeaderGroup){
$headerGroup = [];
foreach(explode("\r\n", $rawHeaderGroup) as $line){
foreach(explode("\r\n", $rawHeaderGroup, limit: PHP_INT_MAX) as $line){
$nameValue = explode(":", $line, 2);
if(isset($nameValue[1])){
$headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]);

View File

@ -69,6 +69,7 @@ abstract class Terminal{
public static string $COLOR_MATERIAL_DIAMOND = "";
public static string $COLOR_MATERIAL_LAPIS = "";
public static string $COLOR_MATERIAL_AMETHYST = "";
public static string $COLOR_MATERIAL_RESIN = "";
private static ?bool $formattingCodes = null;
@ -131,6 +132,7 @@ abstract class Terminal{
self::$COLOR_MATERIAL_DIAMOND = $color(37);
self::$COLOR_MATERIAL_LAPIS = $color(24);
self::$COLOR_MATERIAL_AMETHYST = $color(98);
self::$COLOR_MATERIAL_RESIN = $color(208);
}
protected static function getEscapeCodes() : void{
@ -174,11 +176,12 @@ abstract class Terminal{
self::$COLOR_MATERIAL_DIAMOND = $colors >= 256 ? $setaf(37) : $setaf(14);
self::$COLOR_MATERIAL_LAPIS = $colors >= 256 ? $setaf(24) : $setaf(12);
self::$COLOR_MATERIAL_AMETHYST = $colors >= 256 ? $setaf(98) : $setaf(13);
self::$COLOR_MATERIAL_RESIN = $colors >= 256 ? $setaf(208) : $setaf(11);
}else{
self::$COLOR_BLACK = self::$COLOR_DARK_GRAY = self::$COLOR_MATERIAL_NETHERITE = $setaf(0);
self::$COLOR_RED = self::$COLOR_DARK_RED = self::$COLOR_MATERIAL_REDSTONE = self::$COLOR_MATERIAL_COPPER = $setaf(1);
self::$COLOR_GREEN = self::$COLOR_DARK_GREEN = self::$COLOR_MATERIAL_EMERALD = $setaf(2);
self::$COLOR_YELLOW = self::$COLOR_GOLD = self::$COLOR_MINECOIN_GOLD = self::$COLOR_MATERIAL_GOLD = $setaf(3);
self::$COLOR_YELLOW = self::$COLOR_GOLD = self::$COLOR_MINECOIN_GOLD = self::$COLOR_MATERIAL_GOLD = self::$COLOR_MATERIAL_RESIN = $setaf(3);
self::$COLOR_BLUE = self::$COLOR_DARK_BLUE = self::$COLOR_MATERIAL_LAPIS = $setaf(4);
self::$COLOR_LIGHT_PURPLE = self::$COLOR_PURPLE = self::$COLOR_MATERIAL_AMETHYST = $setaf(5);
self::$COLOR_AQUA = self::$COLOR_DARK_AQUA = self::$COLOR_MATERIAL_DIAMOND = $setaf(6);
@ -253,6 +256,7 @@ abstract class Terminal{
TextFormat::MATERIAL_DIAMOND => Terminal::$COLOR_MATERIAL_DIAMOND,
TextFormat::MATERIAL_LAPIS => Terminal::$COLOR_MATERIAL_LAPIS,
TextFormat::MATERIAL_AMETHYST => Terminal::$COLOR_MATERIAL_AMETHYST,
TextFormat::MATERIAL_RESIN => Terminal::$COLOR_MATERIAL_RESIN,
default => $token,
};
}

View File

@ -73,6 +73,7 @@ abstract class TextFormat{
public const MATERIAL_DIAMOND = TextFormat::ESCAPE . "s";
public const MATERIAL_LAPIS = TextFormat::ESCAPE . "t";
public const MATERIAL_AMETHYST = TextFormat::ESCAPE . "u";
public const MATERIAL_RESIN = TextFormat::ESCAPE . "v";
public const COLORS = [
self::BLACK => self::BLACK,
@ -102,6 +103,7 @@ abstract class TextFormat{
self::MATERIAL_DIAMOND => self::MATERIAL_DIAMOND,
self::MATERIAL_LAPIS => self::MATERIAL_LAPIS,
self::MATERIAL_AMETHYST => self::MATERIAL_AMETHYST,
self::MATERIAL_RESIN => self::MATERIAL_RESIN,
];
public const OBFUSCATED = TextFormat::ESCAPE . "k";
@ -150,7 +152,7 @@ abstract class TextFormat{
* @return string[]
*/
public static function tokenize(string $string) : array{
$result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-u])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-v])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
if($result === false) throw self::makePcreError();
return $result;
}
@ -164,7 +166,7 @@ abstract class TextFormat{
$string = mb_scrub($string, 'UTF-8');
$string = self::preg_replace("/[\x{E000}-\x{F8FF}]/u", "", $string); //remove unicode private-use-area characters (they might break the console)
if($removeFormat){
$string = str_replace(TextFormat::ESCAPE, "", self::preg_replace("/" . TextFormat::ESCAPE . "[0-9a-u]/u", "", $string));
$string = str_replace(TextFormat::ESCAPE, "", self::preg_replace("/" . TextFormat::ESCAPE . "[0-9a-v]/u", "", $string));
}
return str_replace("\x1b", "", self::preg_replace("/\x1b[\\(\\][[0-9;\\[\\(]+[Bm]/u", "", $string));
}
@ -175,7 +177,7 @@ abstract class TextFormat{
* @param string $placeholder default "&"
*/
public static function colorize(string $string, string $placeholder = "&") : string{
return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-u])/u', TextFormat::ESCAPE . '$1', $string);
return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-v])/u', TextFormat::ESCAPE . '$1', $string);
}
/**
@ -252,6 +254,7 @@ abstract class TextFormat{
TextFormat::MATERIAL_DIAMOND => "color:#2cb9a8",
TextFormat::MATERIAL_LAPIS => "color:#20487a",
TextFormat::MATERIAL_AMETHYST => "color:#9a5cc5",
TextFormat::MATERIAL_RESIN => "color:#fc7812",
TextFormat::BOLD => "font-weight:bold",
TextFormat::ITALIC => "font-style:italic",
default => null

View File

@ -369,7 +369,7 @@ final class Utils{
debug_zval_dump($value);
$contents = ob_get_contents();
if($contents === false) throw new AssumptionFailedError("ob_get_contents() should never return false here");
$ret = explode("\n", $contents);
$ret = explode("\n", $contents, limit: 2);
ob_end_clean();
if(preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){
@ -584,10 +584,10 @@ final class Utils{
/**
* @phpstan-template TMemberType
* @phpstan-param array<mixed, TMemberType> $array
* @phpstan-param \Closure(TMemberType) : void $validator
* @phpstan-param \Closure(TMemberType) : mixed $validator
*/
public static function validateArrayValueType(array $array, \Closure $validator) : void{
foreach($array as $k => $v){
foreach(Utils::promoteKeys($array) as $k => $v){
try{
$validator($v);
}catch(\TypeError $e){

View File

@ -360,7 +360,7 @@ class World implements ChunkManager{
private bool $doingTick = false;
/** @phpstan-var class-string<\pocketmine\world\generator\Generator> */
/** @phpstan-var class-string<generator\Generator> */
private string $generator;
private bool $unloaded = false;
@ -375,6 +375,8 @@ class World implements ChunkManager{
private \Logger $logger;
private RuntimeBlockStateRegistry $blockStateRegistry;
/**
* @phpstan-return ChunkPosHash
*/
@ -488,6 +490,7 @@ class World implements ChunkManager{
$this->displayName = $this->provider->getWorldData()->getName();
$this->logger = new \PrefixedLogger($server->getLogger(), "World: $this->displayName");
$this->blockStateRegistry = RuntimeBlockStateRegistry::getInstance();
$this->minY = $this->provider->getWorldMinY();
$this->maxY = $this->provider->getWorldMaxY();
@ -559,7 +562,7 @@ class World implements ChunkManager{
}catch(BlockStateDeserializeException){
continue;
}
$block = RuntimeBlockStateRegistry::getInstance()->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData));
$block = $this->blockStateRegistry->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData));
}else{
//TODO: we probably ought to log an error here
continue;
@ -570,7 +573,7 @@ class World implements ChunkManager{
}
}
foreach(RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $state){
foreach($this->blockStateRegistry->getAllKnownStates() as $state){
$dontTickName = $dontTickBlocks[$state->getTypeId()] ?? null;
if($dontTickName === null && $state->ticksRandomly()){
$this->randomTickBlocks[$state->getStateId()] = true;
@ -1394,7 +1397,7 @@ class World implements ChunkManager{
$entity->onRandomUpdate();
}
$blockFactory = RuntimeBlockStateRegistry::getInstance();
$blockFactory = $this->blockStateRegistry;
foreach($chunk->getSubChunks() as $Y => $subChunk){
if(!$subChunk->isEmptyFast()){
$k = 0;
@ -1528,24 +1531,48 @@ class World implements ChunkManager{
$collides = [];
$collisionInfo = $this->blockStateRegistry->collisionInfo;
if($targetFirst){
for($z = $minZ; $z <= $maxZ; ++$z){
$zOverflow = $z === $minZ || $z === $maxZ;
for($x = $minX; $x <= $maxX; ++$x){
$zxOverflow = $zOverflow || $x === $minX || $x === $maxX;
for($y = $minY; $y <= $maxY; ++$y){
$block = $this->getBlockAt($x, $y, $z);
if($block->collidesWithBB($bb)){
return [$block];
$overflow = $zxOverflow || $y === $minY || $y === $maxY;
$stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
if($overflow ?
$stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW && $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) :
match ($stateCollisionInfo) {
RuntimeBlockStateRegistry::COLLISION_CUBE => true,
RuntimeBlockStateRegistry::COLLISION_NONE => false,
default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb)
}
){
return [$this->getBlockAt($x, $y, $z)];
}
}
}
}
}else{
//TODO: duplicated code :( this way is better for performance though
for($z = $minZ; $z <= $maxZ; ++$z){
$zOverflow = $z === $minZ || $z === $maxZ;
for($x = $minX; $x <= $maxX; ++$x){
$zxOverflow = $zOverflow || $x === $minX || $x === $maxX;
for($y = $minY; $y <= $maxY; ++$y){
$block = $this->getBlockAt($x, $y, $z);
if($block->collidesWithBB($bb)){
$collides[] = $block;
$overflow = $zxOverflow || $y === $minY || $y === $maxY;
$stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
if($overflow ?
$stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW && $this->getBlockAt($x, $y, $z)->collidesWithBB($bb) :
match ($stateCollisionInfo) {
RuntimeBlockStateRegistry::COLLISION_CUBE => true,
RuntimeBlockStateRegistry::COLLISION_NONE => false,
default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb)
}
){
$collides[] = $this->getBlockAt($x, $y, $z);
}
}
}
@ -1555,24 +1582,64 @@ class World implements ChunkManager{
return $collides;
}
/**
* @param int[] $collisionInfo
* @phpstan-param array<int, int> $collisionInfo
*/
private function getBlockCollisionInfo(int $x, int $y, int $z, array $collisionInfo) : int{
if(!$this->isInWorld($x, $y, $z)){
return RuntimeBlockStateRegistry::COLLISION_NONE;
}
$chunk = $this->getChunk($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE);
if($chunk === null){
return RuntimeBlockStateRegistry::COLLISION_NONE;
}
$stateId = $chunk
->getSubChunk($y >> SubChunk::COORD_BIT_SIZE)
->getBlockStateId(
$x & SubChunk::COORD_MASK,
$y & SubChunk::COORD_MASK,
$z & SubChunk::COORD_MASK
);
return $collisionInfo[$stateId];
}
/**
* Returns a list of all block AABBs which overlap the full block area at the given coordinates.
* This checks a padding of 1 block around the coordinates to account for oversized AABBs of blocks like fences.
* Larger AABBs (>= 2 blocks on any axis) are not accounted for.
*
* @param int[] $collisionInfo
* @phpstan-param array<int, int> $collisionInfo
*
* @return AxisAlignedBB[]
* @phpstan-return list<AxisAlignedBB>
*/
private function getBlockCollisionBoxesForCell(int $x, int $y, int $z) : array{
$block = $this->getBlockAt($x, $y, $z);
$boxes = $block->getCollisionBoxes();
private function getBlockCollisionBoxesForCell(int $x, int $y, int $z, array $collisionInfo) : array{
$stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
$boxes = match($stateCollisionInfo){
RuntimeBlockStateRegistry::COLLISION_NONE => [],
RuntimeBlockStateRegistry::COLLISION_CUBE => [AxisAlignedBB::one()->offset($x, $y, $z)],
default => $this->getBlockAt($x, $y, $z)->getCollisionBoxes()
};
$cellBB = AxisAlignedBB::one()->offset($x, $y, $z);
foreach(Facing::OFFSET as [$dx, $dy, $dz]){
$extraBoxes = $this->getBlockAt($x + $dx, $y + $dy, $z + $dz)->getCollisionBoxes();
foreach($extraBoxes as $extraBox){
if($extraBox->intersectsWith($cellBB)){
$boxes[] = $extraBox;
//overlapping AABBs can't make any difference if this is a cube, so we can save some CPU cycles in this common case
if($stateCollisionInfo !== RuntimeBlockStateRegistry::COLLISION_CUBE){
$cellBB = null;
foreach(Facing::OFFSET as [$dx, $dy, $dz]){
$offsetX = $x + $dx;
$offsetY = $y + $dy;
$offsetZ = $z + $dz;
$stateCollisionInfo = $this->getBlockCollisionInfo($offsetX, $offsetY, $offsetZ, $collisionInfo);
if($stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW){
//avoid allocating this unless it's needed
$cellBB ??= AxisAlignedBB::one()->offset($x, $y, $z);
$extraBoxes = $this->getBlockAt($offsetX, $offsetY, $offsetZ)->getCollisionBoxes();
foreach($extraBoxes as $extraBox){
if($extraBox->intersectsWith($cellBB)){
$boxes[] = $extraBox;
}
}
}
}
}
@ -1594,13 +1661,15 @@ class World implements ChunkManager{
$collides = [];
$collisionInfo = $this->blockStateRegistry->collisionInfo;
for($z = $minZ; $z <= $maxZ; ++$z){
for($x = $minX; $x <= $maxX; ++$x){
$chunkPosHash = World::chunkHash($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE);
for($y = $minY; $y <= $maxY; ++$y){
$relativeBlockHash = World::chunkBlockHash($x, $y, $z);
$boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z);
$boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z, $collisionInfo);
foreach($boxes as $blockBB){
if($blockBB->intersectsWith($bb)){
@ -1795,7 +1864,7 @@ class World implements ChunkManager{
return;
}
$blockFactory = RuntimeBlockStateRegistry::getInstance();
$blockFactory = $this->blockStateRegistry;
$this->timings->doBlockSkyLightUpdates->startTiming();
if($this->skyLightUpdate === null){
$this->skyLightUpdate = new SkyLightUpdate(new SubChunkExplorer($this), $blockFactory->lightFilter, $blockFactory->blocksDirectSkyLight);
@ -1914,7 +1983,7 @@ class World implements ChunkManager{
$chunk = $this->chunks[$chunkHash] ?? null;
if($chunk !== null){
$block = RuntimeBlockStateRegistry::getInstance()->fromStateId($chunk->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK));
$block = $this->blockStateRegistry->fromStateId($chunk->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK));
}else{
$addToCache = false;
$block = VanillaBlocks::AIR();
@ -2573,7 +2642,7 @@ class World implements ChunkManager{
$localY = $tilePosition->getFloorY();
$localZ = $tilePosition->getFloorZ() & Chunk::COORD_MASK;
$newBlock = RuntimeBlockStateRegistry::getInstance()->fromStateId($chunk->getBlockStateId($localX, $localY, $localZ));
$newBlock = $this->blockStateRegistry->fromStateId($chunk->getBlockStateId($localX, $localY, $localZ));
$expectedTileClass = $newBlock->getIdInfo()->getTileClass();
if(
$expectedTileClass === null || //new block doesn't expect a tile

View File

@ -0,0 +1,69 @@
<?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\world\biome\model;
/**
* Model for loading biome definition entries data from JSON.
*/
final class BiomeDefinitionEntryData{
/** @required */
public ?int $id;
/** @required */
public float $temperature;
/** @required */
public float $downfall;
/** @required */
public float $redSporeDensity;
/** @required */
public float $blueSporeDensity;
/** @required */
public float $ashDensity;
/** @required */
public float $whiteAshDensity;
/** @required */
public float $depth;
/** @required */
public float $scale;
/** @required */
public ColorData $mapWaterColour;
/** @required */
public bool $rain;
/**
* @required
* @var string[]
* @phpstan-var list<string>
*/
public array $tags;
}

View File

@ -0,0 +1,41 @@
<?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\world\biome\model;
/**
* Model for loading color data from JSON.
*/
final class ColorData{
/** @required */
public int $r;
/** @required */
public int $g;
/** @required */
public int $b;
/** @required */
public int $a;
}

View File

@ -51,12 +51,12 @@ use function time;
class BedrockWorldData extends BaseNbtWorldData{
public const CURRENT_STORAGE_VERSION = 10;
public const CURRENT_STORAGE_NETWORK_VERSION = 776;
public const CURRENT_STORAGE_NETWORK_VERSION = 800;
public const CURRENT_CLIENT_VERSION_TARGET = [
1, //major
21, //minor
60, //patch
33, //revision
80, //patch
3, //revision
0 //is beta
];

View File

@ -26,10 +26,12 @@ namespace pocketmine\world\generator;
use pocketmine\data\bedrock\BiomeIds;
use pocketmine\item\LegacyStringToItemParser;
use pocketmine\item\LegacyStringToItemParserException;
use pocketmine\world\World;
use function array_map;
use function explode;
use function preg_match;
use function preg_match_all;
use const PHP_INT_MAX;
/**
* @internal
@ -70,7 +72,7 @@ final class FlatGeneratorOptions{
*/
public static function parseLayers(string $layers) : array{
$result = [];
$split = array_map('\trim', explode(',', $layers));
$split = array_map('\trim', explode(',', $layers, limit: World::Y_MAX - World::Y_MIN));
$y = 0;
$itemParser = LegacyStringToItemParser::getInstance();
foreach($split as $line){
@ -96,7 +98,7 @@ final class FlatGeneratorOptions{
* @throws InvalidGeneratorOptionsException
*/
public static function parsePreset(string $presetString) : self{
$preset = explode(";", $presetString);
$preset = explode(";", $presetString, limit: 4);
$blocks = $preset[1] ?? "";
$biomeId = (int) ($preset[2] ?? BiomeIds::PLAINS);
$optionsString = $preset[3] ?? "";
@ -109,9 +111,10 @@ final class FlatGeneratorOptions{
$params = true;
if($matches[3][$i] !== ""){
$params = [];
$p = explode(" ", $matches[3][$i]);
$p = explode(" ", $matches[3][$i], limit: PHP_INT_MAX);
foreach($p as $k){
$k = explode("=", $k);
//TODO: this should be limited to 2 parts, but 3 preserves old behaviour when given e.g. treecount=20=1
$k = explode("=", $k, limit: 3);
if(isset($k[1])){
$params[$k[0]] = $k[1];
}

View File

@ -39,7 +39,8 @@ class EntityAttackNoDamageSound implements Sound{
-1,
"minecraft:player",
false,
false
false,
-1
)];
}
}

View File

@ -39,7 +39,8 @@ class EntityAttackSound implements Sound{
-1,
"minecraft:player",
false,
false
false,
-1
)];
}
}

View File

@ -46,7 +46,8 @@ class EntityLandSound implements Sound{
TypeConverter::getInstance()->getBlockTranslator()->internalIdToNetworkId($this->blockLandedOn->getStateId()),
$this->entity::getNetworkTypeId(),
false, //TODO: does isBaby have any relevance here?
false
false,
$this->entity->getId()
)];
}
}

View File

@ -42,7 +42,8 @@ class EntityLongFallSound implements Sound{
-1,
$this->entity::getNetworkTypeId(),
false, //TODO: is isBaby relevant here?
false
false,
$this->entity->getId()
)];
}
}

View File

@ -41,7 +41,8 @@ class EntityShortFallSound implements Sound{
-1,
$this->entity::getNetworkTypeId(),
false, //TODO: does isBaby have any relevance here?
false
false,
-1
)];
}
}

View File

@ -30,6 +30,6 @@ use pocketmine\network\mcpe\protocol\types\LevelSoundEvent;
class ThrowSound implements Sound{
public function encode(Vector3 $pos) : array{
return [LevelSoundEventPacket::create(LevelSoundEvent::THROW, $pos, -1, "minecraft:player", false, false)];
return [LevelSoundEventPacket::create(LevelSoundEvent::THROW, $pos, -1, "minecraft:player", false, false, -1)];
}
}

View File

@ -42,7 +42,8 @@ final class WaterSplashSound implements Sound{
(int) ($this->volume * 16777215),
":",
false,
false
false,
-1
)];
}
}

View File

@ -18,6 +18,12 @@ parameters:
count: 1
path: ../../../src/Server.php
-
message: '#^Dynamic new is not allowed\.$#'
identifier: pocketmine.new.dynamic
count: 1
path: ../../../src/Server.php
-
message: '#^Method pocketmine\\Server\:\:getCommandAliases\(\) should return array\<string, list\<string\>\> but returns array\<string, array\<mixed\>\>\.$#'
identifier: return.type
@ -48,18 +54,18 @@ parameters:
count: 1
path: ../../../src/VersionInfo.php
-
message: '#^Method pocketmine\\VersionInfo\:\:GIT_HASH\(\) should return string but returns mixed\.$#'
identifier: return.type
count: 1
path: ../../../src/VersionInfo.php
-
message: '#^Static property pocketmine\\VersionInfo\:\:\$gitHash \(string\|null\) does not accept mixed\.$#'
identifier: assign.propertyType
count: 1
path: ../../../src/VersionInfo.php
-
message: '#^Dynamic new is not allowed\.$#'
identifier: pocketmine.new.dynamic
count: 1
path: ../../../src/block/Block.php
-
message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#'
identifier: argument.type
@ -516,6 +522,12 @@ parameters:
count: 3
path: ../../../src/block/tile/Spawnable.php
-
message: '#^Dynamic new is not allowed\.$#'
identifier: pocketmine.new.dynamic
count: 1
path: ../../../src/block/tile/TileFactory.php
-
message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#'
identifier: argument.type
@ -918,24 +930,6 @@ parameters:
count: 1
path: ../../../src/plugin/PluginDescription.php
-
message: '#^Parameter \#1 \$haystack of function stripos expects string, mixed given\.$#'
identifier: argument.type
count: 1
path: ../../../src/plugin/PluginDescription.php
-
message: '#^Parameter \#2 \$subject of function preg_match expects string, mixed given\.$#'
identifier: argument.type
count: 1
path: ../../../src/plugin/PluginDescription.php
-
message: '#^Parameter \#3 \$subject of function str_replace expects array\<string\>\|string, mixed given\.$#'
identifier: argument.type
count: 1
path: ../../../src/plugin/PluginDescription.php
-
message: '#^Property pocketmine\\plugin\\PluginDescription\:\:\$authors \(array\<string\>\) does not accept list\<mixed\>\.$#'
identifier: assign.propertyType
@ -961,13 +955,13 @@ parameters:
path: ../../../src/plugin/PluginManager.php
-
message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getPackSize\(\) should return int but returns int\<0, max\>\|false\.$#'
identifier: return.type
message: '#^Dynamic new is not allowed\.$#'
identifier: pocketmine.new.dynamic
count: 1
path: ../../../src/resourcepacks/ZippedResourcePack.php
path: ../../../src/plugin/PluginManager.php
-
message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getSha256\(\) should return string but returns string\|false\.$#'
message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getPackSize\(\) should return int but returns int\<0, max\>\|false\.$#'
identifier: return.type
count: 1
path: ../../../src/resourcepacks/ZippedResourcePack.php
@ -1009,7 +1003,7 @@ parameters:
path: ../../../src/utils/Config.php
-
message: '#^Parameter \#1 \$config of static method pocketmine\\utils\\Config\:\:writeProperties\(\) expects array\<int\|string, bool\|float\|int\|string\>, array\<int\|string, mixed\> given\.$#'
message: '#^Parameter \#1 \$config of static method pocketmine\\utils\\Config\:\:writeProperties\(\) expects array\<int\|string, bool\|float\|int\|string\>, array\<mixed\> given\.$#'
identifier: argument.type
count: 1
path: ../../../src/utils/Config.php
@ -1278,6 +1272,12 @@ parameters:
count: 1
path: ../../../src/world/format/io/region/RegionLoader.php
-
message: '#^Dynamic new is not allowed\.$#'
identifier: pocketmine.new.dynamic
count: 1
path: ../../../src/world/generator/GeneratorRegisterTask.php
-
message: '#^Method pocketmine\\world\\generator\\biome\\BiomeSelector\:\:pickBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#'
identifier: return.type

View File

@ -114,12 +114,6 @@ parameters:
count: 1
path: ../../../src/crash/CrashDump.php
-
message: '#^Call to function assert\(\) with false and ''unknown hit type'' will always evaluate to false\.$#'
identifier: function.impossibleType
count: 1
path: ../../../src/entity/projectile/Projectile.php
-
message: '#^Property pocketmine\\item\\WritableBookBase\:\:\$pages \(list\<pocketmine\\item\\WritableBookPage\>\) does not accept non\-empty\-array\<int, pocketmine\\item\\WritableBookPage\>\.$#'
identifier: assign.propertyType

View File

@ -0,0 +1,55 @@
<?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\phpstan\rules;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\New_;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
/**
* @phpstan-implements Rule<New_>
*/
final class DisallowDynamicNewRule implements Rule{
public function getNodeType() : string{
return New_::class;
}
public function processNode(Node $node, Scope $scope) : array{
/** @var New_ $node */
if($node->class instanceof Expr){
return [
RuleErrorBuilder::message("Dynamic new is not allowed.")
->tip("For factories, use closures instead. Closures can implement custom logic, are statically analyzable, and don't restrict the constructor signature.")
->identifier("pocketmine.new.dynamic")
->build()
];
}
return [];
}
}

View File

@ -0,0 +1,92 @@
<?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\phpstan\rules;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\ArgumentsNormalizer;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function count;
/**
* @phpstan-implements Rule<FuncCall>
*/
final class ExplodeLimitRule implements Rule{
private ReflectionProvider $reflectionProvider;
public function __construct(
ReflectionProvider $reflectionProvider
){
$this->reflectionProvider = $reflectionProvider;
}
public function getNodeType() : string{
return FuncCall::class;
}
public function processNode(Node $node, Scope $scope) : array{
if(!$node->name instanceof Name){
return [];
}
if(!$this->reflectionProvider->hasFunction($node->name, $scope)){
return [];
}
$functionReflection = $this->reflectionProvider->getFunction($node->name, $scope);
if($functionReflection->getName() !== 'explode'){
return [];
}
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
$scope,
$node->getArgs(),
$functionReflection->getVariants(),
$functionReflection->getNamedArgumentsVariants(),
);
$normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node);
if($normalizedFuncCall === null){
return [];
}
$count = count($normalizedFuncCall->getArgs());
if($count !== 3){
return [
RuleErrorBuilder::message('The $limit parameter of explode() must be set to prevent malicious client data wasting resources.')
->identifier("pocketmine.explode.limit")
->build()
];
}
return [];
}
}

View File

@ -41,7 +41,7 @@ use function sprintf;
/**
* @implements Rule<Foreach_>
*/
final class UnsafeForeachArrayOfStringRule implements Rule{
final class UnsafeForeachRule implements Rule{
public function getNodeType() : string{
return Foreach_::class;
@ -73,7 +73,7 @@ final class UnsafeForeachArrayOfStringRule implements Rule{
$benevolentUnionDepth--;
return $result;
}
if($type instanceof IntegerType && $benevolentUnionDepth === 0){
if($type instanceof IntegerType){
$expectsIntKeyTypes = true;
return $type;
}
@ -87,24 +87,31 @@ final class UnsafeForeachArrayOfStringRule implements Rule{
$hasCastableKeyTypes = true;
return $type;
});
if($hasCastableKeyTypes && !$expectsIntKeyTypes){
$tip = $implicitType ?
sprintf(
"Declare a key type using @phpstan-var or @phpstan-param, or use %s() to promote the key type to get proper error reporting",
$errors = [];
if($implicitType){
$errors[] = RuleErrorBuilder::message("Possible unreported errors in foreach on array with unspecified key type.")
->tip(sprintf(
<<<TIP
PHPStan might not be reporting some type errors if the key type is not specified.
Declare a key type using @phpstan-var or @phpstan-param, or use %s() to force PHPStan to report proper errors.
TIP,
Utils::getNiceClosureName(Utils::promoteKeys(...))
) :
sprintf(
"Use %s() to get a \Generator that will force the keys to string",
Utils::getNiceClosureName(Utils::stringifyKeys(...)),
);
return [
RuleErrorBuilder::message(sprintf(
"Unsafe foreach on array with key type %s (they might be casted to int).",
$iterableType->getIterableKeyType()->describe(VerbosityLevel::value())
))->tip($tip)->identifier('pocketmine.foreach.stringKeys')->build()
];
))->identifier('pocketmine.foreach.implicitKeys')->build();
}
return [];
if($hasCastableKeyTypes && !$expectsIntKeyTypes){
$errors[] = RuleErrorBuilder::message(sprintf(
"Unsafe foreach on array with key type %s.",
$iterableType->getIterableKeyType()->describe(VerbosityLevel::value())
))
->tip(sprintf(
<<<TIP
PHP coerces numeric strings to integers when used as array keys, which can lead to unexpected type errors when passing the key to a function.
Use %s() to wrap the array in a \Generator that will force the keys back to string during iteration.
TIP,
Utils::getNiceClosureName(Utils::stringifyKeys(...))
))->identifier('pocketmine.foreach.stringKeys')->build();
}
return $errors;
}
}

View File

@ -51,8 +51,10 @@ use function array_unique;
use function array_values;
use function count;
use function dirname;
use function fclose;
use function file_exists;
use function file_put_contents;
use function fopen;
use function fwrite;
use function get_class;
use function get_debug_type;
@ -885,6 +887,44 @@ function cmdUpdateAll(array $argv) : int{
return 0;
}
/**
* @param string[] $argv
*/
function cmdDumpTable(array $argv) : int{
$tableFile = $argv[2];
$outputFile = $argv[3];
$output = fopen($outputFile, 'wb');
if($output === false){
fwrite(STDERR, "Failed to open output file: $outputFile\n");
return 1;
}
$table = loadUpgradeTableFromFile($tableFile, reverse: false);
ksort($table, SORT_STRING);
foreach(Utils::stringifyKeys($table) as $oldName => $mappings){
fwrite($output, "---------- MAPPING LIST: $oldName ----------\n");
foreach($mappings as $mapping){
$oldNbt = $mapping->old->toVanillaNbt();
$oldNbt->setInt("version", $mapping->new->getVersion());
//intentionally not reused result of toVanillaNbt otherwise output wouldn't include version
fwrite($output, "OLD: " . $mapping->old->toVanillaNbt() . "\n");
if(!$oldNbt->equals($mapping->new->toVanillaNbt())){
fwrite($output, "NEW: " . $mapping->new->toVanillaNbt() . "\n");
}else{
fwrite($output, "NEW: version bump only (" . $mapping->new->getVersion() . ")\n");
}
fwrite($output, "-----\n");
}
}
fclose($output);
\GlobalLogger::get()->info("Table dump file $outputFile generated successfully.");
return 0;
}
/**
* @param string[] $argv
*/
@ -893,7 +933,8 @@ function main(array $argv) : int{
"generate" => [["palette upgrade table file", "schema output file"], cmdGenerate(...)],
"test" => [["palette upgrade table file", "schema output file"], cmdTest(...)],
"update" => [["schema input file", "old palette file", "updated schema output file"], cmdUpdate(...)],
"update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)]
"update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)],
"dump-table" => [["palette upgrade table file", "txt output file"], cmdDumpTable(...)]
];
$selected = $argv[1] ?? null;

View File

@ -54,7 +54,6 @@ use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\StartGamePacket;
use pocketmine\network\mcpe\protocol\types\CacheableNbt;
use pocketmine\network\mcpe\protocol\types\inventory\CreativeGroupEntry;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData;
@ -76,6 +75,8 @@ use pocketmine\network\PacketHandlingException;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use pocketmine\world\biome\model\BiomeDefinitionEntryData;
use pocketmine\world\biome\model\ColorData;
use pocketmine\world\format\io\GlobalBlockStateHandlers;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Symfony\Component\Filesystem\Path;
@ -100,6 +101,7 @@ use function json_encode;
use function ksort;
use function mkdir;
use function ord;
use function round;
use function strlen;
use const FILE_IGNORE_NEW_LINES;
use const JSON_PRETTY_PRINT;
@ -572,34 +574,34 @@ class ParserPacketHandler extends PacketHandler{
public function handleBiomeDefinitionList(BiomeDefinitionListPacket $packet) : bool{
echo "storing biome definitions" . PHP_EOL;
file_put_contents($this->bedrockDataPath . '/biome_definitions_full.nbt', $packet->definitions->getEncodedNbt());
$definitions = [];
foreach($packet->buildDefinitionsFromData() as $entry){
$mapWaterColor = new ColorData();
$mapWaterColor->r = $entry->getMapWaterColor()->getR();
$mapWaterColor->g = $entry->getMapWaterColor()->getG();
$mapWaterColor->b = $entry->getMapWaterColor()->getB();
$mapWaterColor->a = $entry->getMapWaterColor()->getA();
$nbt = $packet->definitions->getRoot();
if(!$nbt instanceof CompoundTag){
throw new AssumptionFailedError();
}
$strippedNbt = clone $nbt;
foreach($strippedNbt as $compound){
if($compound instanceof CompoundTag){
foreach([
"minecraft:capped_surface",
"minecraft:consolidated_features",
"minecraft:frozen_ocean_surface",
"minecraft:legacy_world_generation_rules",
"minecraft:mesa_surface",
"minecraft:mountain_parameters",
"minecraft:multinoise_generation_rules",
"minecraft:overworld_generation_rules",
"minecraft:surface_material_adjustments",
"minecraft:surface_parameters",
"minecraft:swamp_surface",
] as $remove){
$compound->removeTag($remove);
}
}
$data = new BiomeDefinitionEntryData();
$data->id = $entry->getId();
$data->temperature = round($entry->getTemperature(), 3);
$data->downfall = round($entry->getDownfall(), 3);
$data->redSporeDensity = round($entry->getRedSporeDensity(), 3);
$data->blueSporeDensity = round($entry->getBlueSporeDensity(), 3);
$data->ashDensity = round($entry->getAshDensity(), 3);
$data->whiteAshDensity = round($entry->getWhiteAshDensity(), 3);
$data->depth = round($entry->getDepth(), 3);
$data->scale = round($entry->getScale(), 3);
$data->mapWaterColour = $mapWaterColor;
$data->rain = $entry->hasRain();
$data->tags = $entry->getTags() ?? [];
$definitions[$entry->getBiomeName()] = self::objectToOrderedArray($data);
}
file_put_contents($this->bedrockDataPath . '/biome_definitions.nbt', (new CacheableNbt($strippedNbt))->getEncodedNbt());
ksort($definitions, SORT_STRING);
file_put_contents($this->bedrockDataPath . '/biome_definitions.json', json_encode($definitions, JSON_PRETTY_PRINT) . "\n");
return true;
}
@ -624,7 +626,7 @@ function main(array $argv) : int{
}
foreach($packets as $lineNum => $line){
$parts = explode(':', $line);
$parts = explode(':', $line, limit: 3);
if(count($parts) !== 2){
fwrite(STDERR, 'Wrong packet format at line ' . ($lineNum + 1) . ', expected read:base64 or write:base64');
return 1;