Compare commits

...

114 Commits

Author SHA1 Message Date
d273ccf87c Release 5.15.0 2024-04-25 11:52:30 +01:00
737f5066a0 Fully cover codegen in CI 2024-04-25 11:48:22 +01:00
10238d7934 Removed beta change 2024-04-25 11:39:09 +01:00
6077748caa Changes for 1.20.80 2024-04-25 11:31:41 +01:00
50e2c469a5 Bump phpstan/phpstan-strict-rules from 1.5.2 to 1.5.3 (#6326)
Bumps [phpstan/phpstan-strict-rules](https://github.com/phpstan/phpstan-strict-rules) from 1.5.2 to 1.5.3.
- [Release notes](https://github.com/phpstan/phpstan-strict-rules/releases)
- [Commits](https://github.com/phpstan/phpstan-strict-rules/compare/1.5.2...1.5.3)

---
updated-dependencies:
- dependency-name: phpstan/phpstan-strict-rules
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 14:24:59 +01:00
fa87602661 Bump build/php from f9601e5 to 084822a (#6323)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `f9601e5` to `084822a`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](f9601e5313...084822aa9e)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 14:24:48 +01:00
d3c9c137ad Bump symfony/filesystem from 6.4.3 to 6.4.6 (#6327)
Bumps [symfony/filesystem](https://github.com/symfony/filesystem) from 6.4.3 to 6.4.6.
- [Release notes](https://github.com/symfony/filesystem/releases)
- [Changelog](https://github.com/symfony/filesystem/blob/7.0/CHANGELOG.md)
- [Commits](https://github.com/symfony/filesystem/compare/v6.4.3...v6.4.6)

---
updated-dependencies:
- dependency-name: symfony/filesystem
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 14:24:11 +01:00
37322e0d50 Armor: only make sound when the item was equipped by player action
this ensures the greatest amount of consistency with vanilla.

in order to prevent the sounds being broadcasted on armor damage with the old method, we'd also have to sacrifice the sound when replacing one leather helmet with another, for example.
this approach minimizes the gameplay impact at the possible expense of plugins.

closes #6325
2024-04-08 14:05:47 +01:00
55cf24aa02 5.14.2 is next 2024-04-05 18:41:27 +01:00
3590d84d03 Release 5.14.1 2024-04-05 18:41:27 +01:00
68f8fa8caf Update pmmpthread required version 2024-04-05 18:40:43 +01:00
1ad190024a 5.14.1 is next 2024-04-05 18:16:22 +01:00
769a149057 Release 5.14.0 2024-04-05 18:16:19 +01:00
ea339355bb Merge branch 'minor-next' into stable 2024-04-05 17:30:59 +01:00
b9288c238b Update BedrockBlockUpgradeSchema 2024-04-05 17:29:16 +01:00
16f29c775e tools/generate-blockstate-upgrade-schema: added support for generating newFlattenedName with value transforms
as seen in pmmp/BedrockBlockUpgradeSchema@ebd768e5b2, this enables use of newFlattenedName in more places (by allowing the flattened values to be transformed before building the new ID), as well as reducing the number of remappedStates in general by compacting stuff which was partially transformed like color silver -> light_gray.
2024-04-05 17:13:38 +01:00
e30e27dd57 Fix CS 2024-04-03 15:43:43 +01:00
cd6634d34b Bump shivammathur/setup-php from 2.30.0 to 2.30.2 (#6315)
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.30.0 to 2.30.2.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.30.0...2.30.2)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 15:37:13 +01:00
f013079ff6 Fixed MainLogger BC break 2024-04-03 15:31:37 +01:00
c4abac4606 Bump build/php from 6f619bf to f9601e5 (#6321)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `6f619bf` to `f9601e5`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](6f619bf7a0...f9601e5313)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 14:03:46 +01:00
11fbc8db6f Make use of pmmpthread 6.1.0 for Thread::getRunningCount()
ThreadManager doesn't count these correctly anymore since pmmpthread since thread-safe statics aren't copied anymore.
2024-04-02 19:40:44 +01:00
022362a01a Update pocketmine/errorhandler to 0.7.0 2024-04-02 19:37:07 +01:00
98380e46bf Ignore non-fatal error types in crash handlers
ErrorToExceptionHandler currently prevents these from appearing by turning them into exceptions, but this won't always be the case.
For example, in the future we may not want to turn ALL types of E_* errors into exceptions (e.g. E_DEPRECATED).
2024-04-02 19:22:40 +01:00
dad9a7e6cd Merge branch 'stable' into minor-next 2024-04-02 16:35:31 +01:00
de6a91dabc Rework consistency check to tolerate dynamic type IDs
we don't actually care about the specific values, only whether all the blocks and their states have been correctly registered.
I'd prefer to track all of the state data permutations, but the APIs for that are private, so tracking the number of permutations will have to suffice (this should be good enough to detect bugs anyway, and also takes way less space).
2024-04-01 18:44:01 +01:00
0615afa766 Bump phpstan/phpstan from 1.10.65 to 1.10.66 (#6317)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.65 to 1.10.66.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.65...1.10.66)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 16:12:31 +01:00
d5919dc094 ... 2024-03-25 17:24:52 +00:00
09904dc519 workaround for callback-validator not understanding arrow functions 2024-03-25 14:58:21 +00:00
f799cfaba6 Implemented sound when equipping armor (#6303) 2024-03-25 14:15:54 +00:00
11f119551d Bump phpstan/phpstan from 1.10.62 to 1.10.65 (#6308)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.62 to 1.10.65.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.62...1.10.65)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 10:55:22 +00:00
2584314202 NetherRoots: fix placement on soul soil (#6299) 2024-03-25 10:53:16 +00:00
337e462c8f Added new banner patterns (#6298) 2024-03-25 10:52:21 +00:00
b680a1693c Added sound when picking sweet berries (#6287) 2024-03-19 10:18:30 +00:00
0e5395c59b PocketMine-MP.phar self-extraction to decompressed cache (#6217)
Because ext-phar sucks, tmp gets spammed by cache files for every thread when loading files from the phar on the fly.

Instead, we convert the `.phar` into a decompressed `.tar` in the tmp directory and require files from inside it. Surprisingly, this works because `ext-phar` supports `tar` and `zip` natively. No stream wrapper is required, as the `PocketMine.php` bootstrap loads files relative to its location, so the cache is automatically used for everything.

To be honest I would rather get rid of phars entirely, but they are still the easiest way to have PhpStorm load PocketMine-MP API information for now, and the alternatives are more complicated and inconvenient.

### Caveats
Everywhere that previously used `new Phar(Phar::running(false))` in the core code needs to be updated to use `PharData` for this to work correctly. Plugins don't need to do anything.

### Why not just use `Phar::decompressFiles()`?
This requires setting `phar.readonly` to `0`, which is a security issue. Technically, we could have used a subprocess to do this, but it just didn't seem right.

### WTF? `phar://` can be used on `tar` files???
Yup. I was just as surprised to find out that `require` works in such contexts.

### Relevant issues
- Closes #6214 

## Changes
### API changes
None.

### Behavioural changes
Server startup will be slightly slower, as the phar has to decompress and convert itself into a `.tar`. However, testing showed that this generally takes less than 200 ms, so it should be barely noticeable.

## Backwards compatibility
No BC issues.

## Tests
Locally tested and the CI will also verify
2024-03-18 16:48:17 +00:00
94e0bf954b Bump docker/build-push-action from 5.2.0 to 5.3.0 (#6288)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-18 15:08:13 +00:00
556b00d11f Bump phpstan/phpstan from 1.10.60 to 1.10.62 (#6289)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.60 to 1.10.62.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.60...1.10.62)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-18 15:06:50 +00:00
981f49ff56 CS 2024-03-15 18:03:44 +00:00
f527a4c8fe Added --no-log-file command line option
while this would be more user-friendly as a config option, configs are a pain because they aren't initialized until after the server log has already been set up.
In any case, I foresee that people will likely want to bake this into Dockerfiles directly anyway.
2024-03-15 17:53:50 +00:00
7148c7a222 Log rotate (#4032)
`server.log` is not rotated by default and grows unmanageably large, to the point where it's so huge that it's not possible to read it with any standard text editor anymore.

This PR implements automatic log rotation. 
- When the `server.log` reaches 32MB in size, it's moved to the `log_archive/` folder of the server's data folder.
- The archive's file name will look something like this: `server.2024-03-15T15.26.24.0.log`
- The file's name contains the date and time when the file was archived. This may be useful if you're trying to find logs from a particular time frame.

This has several benefits:
- Much more easily find logs from a particular time frame without scrolling through GBs of logs
- Free up space without stopping the server - Archived log files in `log_archive/` can be safely deleted and/or modified while the server is running

If you want to automatically compress or clean up the log files, I suggest an external cron job or disk watcher.

Closes #4029.
2024-03-15 16:44:37 +00:00
e31fd122d9 BlockStateReader: micro optimize unread properties check
this has a pretty much insignificant performance impact, but reduces the cost of this check to basically 0.
2024-03-14 17:54:26 +00:00
a835069564 Merge remote-tracking branch 'origin/stable' into minor-next 2024-03-14 12:47:04 +00:00
b77193b987 ZlibCompressor: Increase max decompression size to accommodate larger skins
again, very annoying that we have to account for this (it makes it easier for attackers to waste CPU time and memory), but we don't really have much of a choice.
2024-03-14 12:34:30 +00:00
11ca208d93 RakLib: Allow larger number of split packet parts
some persona skins are insanely bloated and get split into hundreds of parts.
it's quite annoying that we have to accommodate this, but we can't keep allowing players to experience login timeouts without an obvious indication what's wrong.
2024-03-14 12:32:26 +00:00
8d7f1a8557 BlockStateUpgraderTest: fixed tests for 7ff0ae19d6 2024-03-13 18:35:07 +00:00
7ff0ae19d6 BlockStateUpgrader: a simple yet hard-to-explain optimization
Prior to this commit, upgrade schemas would be applied to blockstates with the same version, as there wasn't any way to know if they should be applied or not given Mojang's tendency to forget to bump the version.
However, it occurred to me that we only need to do this if there are multiple schemas present for the same version ID, which is rarely the case.
This allows skipping costly logic for blockstates on the newest version (the common case), reducing the time taken to process the blockstate for upgrading by over 30%.
Overall, this translates into less than 10% real performance improvement for chunk loading, but it's still a worthwhile improvement.
2024-03-13 18:19:51 +00:00
1de66cb0de RegistryTrait: added fast path optimization
this reduces VanillaBlocks access time from 360 ns to 230 ns on my machine - an improvement of about 35%.
2024-03-13 17:11:06 +00:00
9f3533d870 Improved logging for block decode errors
this is still noisy, but less so than before.
this also adds logging to places where it was previously missing.
2024-03-13 16:42:23 +00:00
2d24fac067 5.13.1 is next 2024-03-13 14:59:21 +00:00
f193a990b0 Release 5.13.0 2024-03-13 14:59:21 +00:00
c11c0679e3 Fix CS 2024-03-13 14:55:54 +00:00
ba48f258f3 Support for 1.20.70 2024-03-13 14:53:27 +00:00
e105c9bd76 5.12.2 is next 2024-03-13 13:48:59 +00:00
23f4632409 Release 5.12.1 2024-03-13 13:48:56 +00:00
264ce06cbf Updated composer dependencies 2024-03-12 16:51:12 +00:00
a6202d0442 BlockStateUpgrader: calculate output version ID in a less stupid way
this improves the performance by a conservative 10%.
2024-03-12 11:48:48 +00:00
8ec304e66e BlockStateUpgradeSchema: avoid unnecessary property access and calculation
this was costing a surprisingly large 5-10% of the processing time for blockstate data.
2024-03-12 11:45:08 +00:00
cbffbd23f9 Bump docker/build-push-action from 5.1.0 to 5.2.0 (#6281)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.1.0 to 5.2.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5.1.0...v5.2.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-12 09:30:29 +00:00
9d7aec5891 Bump phpstan/phpstan from 1.10.59 to 1.10.60 (#6282)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.59 to 1.10.60.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.59...1.10.60)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-12 09:30:03 +00:00
ac8dbf8640 BlockStateUpgrader: extract state remap to its own function 2024-03-06 12:56:49 +00:00
dbc7105e5b Merge branch 'resource-pack-ack-receipts' into minor-next 2024-03-04 15:46:31 +00:00
3b97d067a3 Merge remote-tracking branch 'origin/stable' into minor-next 2024-03-04 15:40:10 +00:00
b0390a39fd Update composer dependencies 2024-03-04 15:17:48 +00:00
5cb69e00d0 NetworkSession: remove hardcoded limit
this is already covered by the dynamic Game Packets rate limit, which is much more effective at dealing with this anyway.
2024-03-04 14:36:40 +00:00
781e3643dd Clean up 2024-03-04 14:25:47 +00:00
2ca50ecd36 Bump shivammathur/setup-php from 2.29.0 to 2.30.0 (#6273)
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.29.0 to 2.30.0.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.29.0...2.30.0)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 11:54:53 +00:00
5ad63f27bb Update RakLib (again) 2024-03-02 01:02:57 +00:00
f13eaaab05 Update RakLib 2024-03-02 00:08:49 +00:00
72f3c0b4b9 NetworkSession: fixed timings not being stopped when handling uncompressed packets 2024-03-01 17:36:40 +00:00
b9a1ef1357 Throttle resource pack sending using ack receipts
this isn't the best solution, as it limits the download speed somewhat, but it's relatively simple and works quite well.
closes #3127
2024-03-01 17:07:19 +00:00
4abc36275c Remove newline 2024-03-01 17:02:44 +00:00
4b5ac53276 Fixes 2024-03-01 17:01:32 +00:00
90409b50d1 Allow offering different resource packs to different players (#6249)
closes #6248
2024-03-01 14:53:59 +00:00
bc2abf4b15 First shot at packet ack receipt support
this will be useful for preventing resource pack sending from overloading the network.
it's not the best solution for that (since it means the RTT will limit the pack download speed), but it's easier than implementing congestion control and will work fine in most cases.
2024-03-01 14:41:53 +00:00
b2c97cf2f1 5.12.1 is next 2024-02-28 18:14:41 +00:00
a35c3406a8 Release 5.12.0 2024-02-28 18:14:37 +00:00
54ea404d80 Merge branch 'minor-next' into stable 2024-02-28 17:39:25 +00:00
98042f844f Merge remote-tracking branch 'origin/stable' into minor-next 2024-02-28 17:36:21 +00:00
a0cca53f52 Fixed mismatched predictions due to NBT key order differences
this is a pain :(
It appears the client always sorts the keys in alphabetical order due to use of std::map. However I'm not sure of the exact ordering behaviour, so it needs to be investigated.
2024-02-27 16:07:43 +00:00
6872118355 Update BedrockProtocol to release version 2024-02-27 14:15:31 +00:00
efd113bdc8 Integrate pmmp/BedrockProtocol@65b3d0b341 2024-02-26 17:09:09 +00:00
34a5f91aa9 5.11.3 is next 2024-02-26 14:45:48 +00:00
920341668f Implemented working Name tag (#5209) 2024-02-19 18:46:48 +00:00
4fab518384 PluginManager: do not accept generator functions as event handlers
closes #4912

I didn't merge the original PR because this needs to be checked for explicitly registered handlers as well as auto-detected ones from listeners.
2024-02-19 16:53:53 +00:00
2616d8c5ad New biome IDs, courtesy of build/generate-biome-ids 2024-02-19 16:10:46 +00:00
61d0181bfd Added script to generate biome IDs
this has been sitting in my local workspace for a very long time
2024-02-19 16:10:22 +00:00
d211392b67 Merge remote-tracking branch 'origin/stable' into minor-next 2024-02-12 11:46:48 +00:00
6bb84bc46c Add Promise::all (#6152) 2024-02-06 12:42:24 +00:00
20837c9894 Merge remote-tracking branch 'origin/stable' into minor-next 2024-02-05 12:43:39 +00:00
f207d1bbf2 Make CocoaBlock Flowable (#6218) 2024-02-05 12:36:09 +00:00
c7c20d4d79 tools/generate-block-palette-spec: fixed sorting 2024-01-09 16:43:11 +00:00
c6a09e5ed8 Merge branch 'stable' into minor-next 2024-01-09 16:17:55 +00:00
a459e3c1a9 Block: improve some documentation 2024-01-09 13:35:36 +00:00
288bd4018b Block: deprecate isSolid()
As discussed many years ago in #2551, no one actually knows what this property actually means. It definitely isn't the conventionally expected definition of 'solid' found in the real world, as signs are solid but flower pots are not.
2024-01-09 13:35:10 +00:00
9b03b082ab Added --version option 2024-01-09 13:04:14 +00:00
db3bb55a2b Change PHP_DEBUG constant usage to ZEND_DEBUG_BUILD
In PHP 8.4, the type of `PHP_DEBUG` changes from `int` to `bool`.
See [PHP.Watch: PHP 8.4: `PHP_ZTS` and `PHP_DEBUG` constant value type changed from `int` to `bool`](https://php.watch/versions/8.4/PHP_ZTS-PHP_DEBUG-const-type-change).

This changes the constants to `ZEND_DEBUG_BUILD`, which contains the same value but as a `bool` across all PHP versions.

closes #6222
2024-01-09 12:30:24 +00:00
8372c9efc2 Merge branch 'stable' into minor-next 2024-01-09 12:27:25 +00:00
5386e86079 ProcessLoginTask: remove old root key (#6211) 2024-01-03 12:50:05 +00:00
1b0ef468f3 CommonThreadPartsTrait: remove outdated documentation
This is now automatically called by the final run(), and the user now only needs to implement onRun(), so they have no business calling this function.
2023-12-21 13:09:05 +00:00
b69843a8bd CommonThreadPartsTrait: add common implementation of quit()
there's no need for the worker specialization here (isShutdown and shutdown are aliased to isJoined and join respectively), and the unstacking is not really desirable either as we previously learned with AsyncPool.
2023-12-21 12:56:51 +00:00
03619ebca9 Thread/Worker: Remove duplicated code
Despite the comments, there doesn't seem to be an obvious reason for these to be copy-pasted. Perhaps there was some legacy reason for this with legacy pthreads.
In fact, it looks likely that quit() will probably be able to be traitified too.
2023-12-21 12:44:03 +00:00
fd1bc1b845 AsyncWorker: deprecate ThreadStore methods
these are inconvenient and don't make any sense. It's far easier and more static-analysis-friendly to just use static properties.
2023-12-21 12:39:55 +00:00
c05116849a AsyncWorker: clean up nonsensical sleeper notifier handling code 2023-12-21 12:39:12 +00:00
bf99917f2a ThreadSafeClassLoader: add native property types 2023-12-20 17:01:20 +00:00
57f3a04bc5 data: Use statically analyzable ways of ensuring all cases are registered
PHPStan will verify that these matches cover all cases, which guarantees that all cases will be covered.
In addition, if PHPStan is not used, the constructors will immediately bail out when they hit a case that isn't covered.
The only downside is the extra indentation :(
2023-12-20 16:07:05 +00:00
c51b1b2812 Create LightableTrait and remove repetitive code (#6111) 2023-12-20 15:21:11 +00:00
80125f9b19 Modernize single-use phpstan-template tags 2023-12-20 15:20:28 +00:00
8dc28b7ea8 RuntimeDataDescriber: remove useless template parameter 2023-12-20 15:15:43 +00:00
58ce746ae1 Remove dead PHPStan ignored error 2023-12-20 14:44:24 +00:00
74cb0be868 Noise: give PHPStan some help understanding SplFixedArray 2023-12-20 14:43:36 +00:00
4d9b97d2bb Merge branch 'stable' into minor-next 2023-12-20 14:30:38 +00:00
c8da9dea95 WorldManager: Remove unused if in unloadWorld() (#6203) 2023-12-20 10:38:00 +00:00
e1f4fd3048 ProcessLoginTask: remove dead comments
This is no longer an issue since b2df405cc0.
2023-12-15 16:01:43 +00:00
d3d7f24015 Noise: make calls with many parameters less nauseating to read 2023-12-15 15:32:54 +00:00
944dd7d3e4 BaseBanner: remove unnecessary array_filter() usage 2023-12-15 15:19:44 +00:00
133 changed files with 3509 additions and 1222 deletions

View File

@ -53,7 +53,7 @@ jobs:
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
- name: Build image for tag
uses: docker/build-push-action@v5.1.0
uses: docker/build-push-action@v5.3.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@v5.1.0
uses: docker/build-push-action@v5.3.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@v5.1.0
uses: docker/build-push-action@v5.3.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@v5.1.0
uses: docker/build-push-action@v5.3.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.29.0
uses: shivammathur/setup-php@2.30.2
with:
php-version: 8.2

View File

@ -20,7 +20,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.29.0
uses: shivammathur/setup-php@2.30.2
with:
php-version: ${{ matrix.php-version }}

View File

@ -147,17 +147,8 @@ jobs:
- name: Install Composer dependencies
run: composer install --no-dev --prefer-dist --no-interaction
- name: Regenerate registry annotations
run: php build/generate-registry-annotations.php src
- name: Regenerate KnownTranslation APIs
run: php build/generate-known-translation-apis.php
- name: Regenerate BedrockData available files constants
run: php build/generate-bedrockdata-path-consts.php
- name: Regenerate YmlServerProperties constants
run: php build/generate-pocketmine-yml-property-consts.php
- name: Update generated code
run: composer update-codegen
- name: Verify code is unchanged
run: |

View File

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

View File

@ -0,0 +1,133 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\build\generate_biome_ids;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use function asort;
use function dirname;
use function fclose;
use function fopen;
use function fwrite;
use function is_array;
use function is_int;
use function is_string;
use function json_decode;
use function str_replace;
use function strtoupper;
use const SORT_NUMERIC;
require dirname(__DIR__) . '/vendor/autoload.php';
const HEADER = <<<'HEADER'
<?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);
HEADER;
/** @return resource */
function safe_fopen(string $file, string $flags){
$result = fopen($file, $flags);
if($result === false){
throw new \RuntimeException("Failed to open file");
}
return $result;
}
function make_const_name(string $name) : string{
return strtoupper(str_replace(['.', 'minecraft:'], ['_', ''], $name));
}
/**
* @param int[] $map
* @phpstan-param array<string, int> $map
*/
function generate(array $map, string $outputFile) : void{
$file = safe_fopen($outputFile, 'wb');
fwrite($file, HEADER);
fwrite($file, <<<'CLASSHEADER'
namespace pocketmine\data\bedrock;
final class BiomeIds{
private function __construct(){
//NOOP
}
CLASSHEADER
);
$list = $map;
asort($list, SORT_NUMERIC);
$lastId = -1;
foreach(Utils::stringifyKeys($list) as $name => $id){
if($name === ""){
continue;
}
if($id !== $lastId + 1){
fwrite($file, "\n");
}
$lastId = $id;
fwrite($file, "\tpublic const " . make_const_name($name) . ' = ' . $id . ';' . "\n");
}
fwrite($file, "}\n");
fclose($file);
}
$ids = json_decode(Filesystem::fileGetContents(BedrockDataFiles::BIOME_ID_MAP_JSON), true);
if(!is_array($ids)){
throw new \RuntimeException("Invalid biome ID map, expected array for root JSON object");
}
$cleanedIds = [];
foreach($ids as $name => $id){
if(!is_string($name) || !is_int($id)){
throw new \RuntimeException("Invalid biome ID map, expected string => int map");
}
$cleanedIds[$name] = $id;
}
generate($cleanedIds, dirname(__DIR__) . '/src/data/bedrock/BiomeIds.php');
echo "Done. Don't forget to run CS fixup after generating code.\n";

View File

@ -102,6 +102,25 @@ function generateClassHeader(string $className) : string{
return <<<HEADER
<?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 $namespace;

View File

@ -61,6 +61,25 @@ function generateItemIds(ItemTypeDictionary $dictionary, BlockItemIdMap $blockIt
fwrite($file, <<<'HEADER'
<?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\data\bedrock\item;

168
build/server-phar-stub.php Normal file
View File

@ -0,0 +1,168 @@
<?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\server_phar_stub;
use function clearstatcache;
use function copy;
use function fclose;
use function fflush;
use function flock;
use function fopen;
use function fwrite;
use function getmypid;
use function hrtime;
use function is_dir;
use function is_file;
use function mkdir;
use function number_format;
use function str_replace;
use function stream_get_contents;
use function sys_get_temp_dir;
use function tempnam;
use function unlink;
use const DIRECTORY_SEPARATOR;
use const LOCK_EX;
use const LOCK_NB;
use const LOCK_UN;
/**
* Finds the appropriate tmp directory to store the decompressed phar cache, accounting for potential file name
* collisions.
*/
function preparePharCacheDirectory() : string{
clearstatcache();
$i = 0;
do{
$tmpPath = sys_get_temp_dir() . '/PocketMine-MP-phar-cache.' . $i;
$i++;
}while(is_file($tmpPath));
if(!@mkdir($tmpPath) && !is_dir($tmpPath)){
throw new \RuntimeException("Failed to create temporary directory $tmpPath. Please ensure the disk has enough space and that the current user has permission to write to this location.");
}
return $tmpPath;
}
/**
* Deletes caches left behind by previous server instances.
* This ensures that the tmp directory doesn't get flooded by servers crashing in restart loops.
*/
function cleanupPharCache(string $tmpPath) : void{
clearstatcache();
/** @var string[] $matches */
foreach(new \RegexIterator(
new \FilesystemIterator(
$tmpPath,
\FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS
),
'/(.+)\.lock$/',
\RegexIterator::GET_MATCH
) as $matches){
$lockFilePath = $matches[0];
$baseTmpPath = $matches[1];
$file = @fopen($lockFilePath, "rb");
if($file === false){
//another process probably deleted the lock file already
continue;
}
if(flock($file, LOCK_EX | LOCK_NB)){
//this tmpfile is no longer in use
flock($file, LOCK_UN);
fclose($file);
unlink($lockFilePath);
unlink($baseTmpPath . ".tar");
unlink($baseTmpPath);
echo "Deleted stale phar cache at $baseTmpPath\n";
}else{
$pid = stream_get_contents($file);
fclose($file);
echo "Phar cache at $baseTmpPath is still in use by PID $pid\n";
}
}
}
function convertPharToTar(string $tmpName, string $pharPath) : string{
$tmpPharPath = $tmpName . ".phar";
copy($pharPath, $tmpPharPath);
$phar = new \Phar($tmpPharPath);
//phar requires phar.readonly=0, and zip doesn't support disabling compression - tar is the only viable option
//we don't need phar anyway since we don't need to directly execute the file, only require files from inside it
$phar->convertToData(\Phar::TAR, \Phar::NONE);
unset($phar);
\Phar::unlinkArchive($tmpPharPath);
return $tmpName . ".tar";
}
/**
* Locks a phar tmp cache to prevent it from being deleted by other server instances.
* This code looks similar to Filesystem::createLockFile(), but we can't use that because it's inside the compressed
* phar.
*/
function lockPharCache(string $lockFilePath) : void{
//this static variable will keep the file(s) locked until the process ends
static $lockFiles = [];
$lockFile = fopen($lockFilePath, "wb");
if($lockFile === false){
throw new \RuntimeException("Failed to open temporary file");
}
flock($lockFile, LOCK_EX); //this tells other server instances not to delete this cache file
fwrite($lockFile, (string) getmypid()); //maybe useful for debugging
fflush($lockFile);
$lockFiles[$lockFilePath] = $lockFile;
}
/**
* Prepares a decompressed .tar of PocketMine-MP.phar in the system temp directory for loading code from.
*
* @return string path to the temporary decompressed phar (actually a .tar)
*/
function preparePharCache(string $tmpPath, string $pharPath) : string{
clearstatcache();
$tmpName = tempnam($tmpPath, "PMMP");
if($tmpName === false){
throw new \RuntimeException("Failed to create temporary file");
}
lockPharCache($tmpName . ".lock");
return convertPharToTar($tmpName, $pharPath);
}
$tmpDir = preparePharCacheDirectory();
cleanupPharCache($tmpDir);
echo "Preparing PocketMine-MP.phar decompressed cache...\n";
$start = hrtime(true);
$cacheName = preparePharCache($tmpDir, __FILE__);
echo "Cache ready at $cacheName in " . number_format((hrtime(true) - $start) / 1e9, 2) . "s\n";
require 'phar://' . str_replace(DIRECTORY_SEPARATOR, '/', $cacheName) . '/src/PocketMine.php';

View File

@ -23,7 +23,9 @@ declare(strict_types=1);
namespace pocketmine\build\server_phar;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Git;
use Symfony\Component\Filesystem\Path;
use function array_map;
use function count;
use function dirname;
@ -169,21 +171,7 @@ function main() : void{
'git' => $gitHash,
'build' => $build
],
<<<'STUB'
<?php
$tmpDir = sys_get_temp_dir();
if(!is_readable($tmpDir) or !is_writable($tmpDir)){
echo "ERROR: tmpdir $tmpDir is not accessible." . PHP_EOL;
echo "Check that the directory exists, and that the current user has read/write permissions for it." . PHP_EOL;
echo "Alternatively, set 'sys_temp_dir' to a different directory in your php.ini file." . PHP_EOL;
exit(1);
}
require("phar://" . __FILE__ . "/src/PocketMine.php");
__HALT_COMPILER();
STUB
,
Filesystem::fileGetContents(Path::join(__DIR__, 'server-phar-stub.php')) . "\n__HALT_COMPILER();",
\Phar::SHA1,
\Phar::GZ
) as $line){

63
changelogs/5.12.md Normal file
View File

@ -0,0 +1,63 @@
# 5.12.0
Released 28th February 2024
**For Minecraft: Bedrock Edition 1.20.60**
This is a minor feature release, with a few new features and improvements.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Added a `--version` command-line option to display the server version and exit.
## Tools
- Added `tools/generate-biome-ids.php` to generate `pocketmine\data\bedrock\BiomeIds`.
- Fixed ordering of property values generated by `tools/generate-block-palette-spec.php`.
## API
### `pocketmine\block`
- The following new classes have been added:
- `utils\LightableTrait` - used by blocks with `getLit()` and `setLit()` methods
- The following methods have been deprecated:
- `Block->isSolid()` - this method returns confusing results which don't match expectations and no one really knows what it actually means
- `CocoaBlock` now extends `Flowable` to match vanilla Minecraft behaviour.
### `pocketmine\plugin`
- `PluginManager->registerEvent()` now throws an exception when given a generator function for the event handler.
- `PluginManager->registerEvents()` now throws an exception if any of the detected event handlers are generator functions. Use `@notHandler` to have the function ignored if intended.
### `pocketmine\promise`
- The following methods have been added:
- `public static Promise::all(list<Promise> $promises) : Promise` - returns a promise that is resolved once all given promises are resolved, or is rejected if any of the promises are rejected.
### `pocketmine\scheduler`
- The following methods have been deprecated:
- `AsyncWorker->getFromThreadStore()` - use class static properties for thread-local storage
- `AsyncWorker->removeFromThreadStore()`
- `AsyncWorker->saveToThreadStore()`
## Documentation
- Improved documentation of various methods in `Block`.
## Gameplay
- The following new items have been added:
- Name Tag
## Internals
- Removed specialization of shutdown logic for `Thread` vs `Worker` (no specialization is required).
- Authentication system no longer accepts logins signed with the old Mojang root public key.
- ID to enum mappings in `pocketmine\data` now use a new `match` convention to allow static analysis to ensure that all enum cases are handled.
- Updated version of `pocketmine/bedrock-protocol` allows avoiding decoding of some itemstack data from the client in most cases, improving performance.
# 5.12.1
Released 13th March 2024.
## Fixes
- Fixed `Player Network Receive - Decompression` timings not being stopped correctly when receiving an uncompressed packet.
## Internals
- Removed hardcoded batch packet size limit. This was already covered by other limits anyway.

16
changelogs/5.13.md Normal file
View File

@ -0,0 +1,16 @@
# 5.13.0
Released 13th March 2024.
**For Minecraft: Bedrock Edition 1.20.70**
This is a support release for Minecraft: Bedrock Edition 1.20.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.
## General
- Added support for Minecraft: Bedrock Edition 1.20.70.
- Removed support for earlier versions.

94
changelogs/5.14.md Normal file
View File

@ -0,0 +1,94 @@
# 5.14.0
Released 5th April 2024.
**For Minecraft: Bedrock Edition 1.20.70**
This is a minor feature release, including performance improvements, minor gameplay features, new API features, and various internal improvements.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Added support for a `--no-log-file` command-line option, which disables the creation of a `server.log` file.
- **Use this with caution.** If you don't have another mechanism for collecting logs (e.g. Docker), this may make debugging harder.
- Added support for automatic `server.log` rotation. When the `server.log` exceeds 32 MB, it will be renamed and moved to the `log_archive` folder in the server's data directory.
- Files in the `log_archive` folder can be safely modified or deleted without stopping the server.
- We suggest a cron job or similar to manage old log files (e.g. deleting or compressing them).
- Added a new cache mechanism for `PocketMine-MP.phar`. This has several advantages:
- Caches are now reused by all threads - this significantly reduces `/tmp` usage (previously every thread generated its own cache, wasting lots of space)
- Dead cache files are automatically cleaned up by new servers - this means that a server crash loop won't flood `/tmp` anymore
- `/status` now reports a more accurate number of threads on Windows.
- Large resource packs are now able to be properly downloaded from the server.
- Larger player skin sizes are now accepted by the server.
- Improved logging from world providers to reduce spam when chunks contain invalid data.
- Added more error logging for Anvil, PMAnvil and MCRegion worlds.
- PHP deprecation warnings no longer cause the server to crash. This should make it easier for server owners to update to newer PHP versions.
## Performance
- Improved world loading performance. This was achieved through a combination of changes:
- Improvements to `BlockStateUpgrader` to avoid unnecessary work
- Improvements to `BlockStateUpgradeSchema` to clean up stupid code
- Improvements to `BlockStateReader` unused state handling
- Optimizations to `RegistryTrait` (see below)
- Improved performance of `RegistryTrait::__callStatic()` accessor by introducing a fast-path optimization. Ensure that you access registries with the correct function name case to benefit from this.
- This improves the performance of `VanillaBlocks::WHATEVER()`, `VanillaItems`, etc.
## Tools
- `tools/generate-blockstate-upgrade-schema.php` now supports generating schemas using `flattenedValueRemaps` (described in [BlockStateUpgradeSchema](https://github.com/pmmp/BedrockBlockUpgradeSchema/releases/tag/4.0.0)).
## Gameplay
- Added sounds for armour equipping and unequipping.
- Added sound for picking berries from a sweet berry bush.
## API
### `pocketmine\block\utils`
- The following enum cases have been added:
- `BannerPatternType::GLOBE`
- `BannerPatternType::PIGLIN`
### `pocketmine\event\player`
- The following classes have been added:
- `PlayerResourcePackOfferEvent` - called before the server tells a connecting client which resource packs are available to download - allows customizing the pack list and other options
### `pocketmine\item`
- The following API methods have been added:
- `public ArmorMaterial->getEquipSound() : ?\pocketmine\world\Sound` - returns the sound to play when this armour is equipped or unequipped
- The following API methods have signature changes:
- `ArmorMaterial->__construct()` now accepts an optional `?Sound $equipSound` parameter
### `pocketmine\utils`
- The following API methods have signature changes:
- `MainLogger->__construct()` now accepts `null` for the `$logFile` parameter - this disables the creation of a logger thread and log file
- `MainLogger->__construct()` now accepts an optional `?string $logArchiveDir` parameter. If set, this enables log archiving in the specified directory when the current log file exceeds 32 MB.
## Dependencies
- Now uses [`pocketmine/bedrock-block-upgrade-schema` version 4.0.0](https://github.com/pmmp/BedrockBlockUpgradeSchema/releases/tag/4.0.0).
- Now uses [`pmmp/ext-pmmpthread` version 6.1.0](https://github.com/pmmp/ext-pmmpthread/releases/tag/6.1.0).
- Now uses [`pocketmine/errorhandler` version 0.7.0](https://github.com/pmmp/ErrorHandler/releases/tag/0.7.0).
- Now uses [`pocketmine/raklib` version 1.1.0](https://github.com/pmmp/RakLib/releases/tag/1.1.0).
- Now uses [`pocketmine/raklib-ipc` version 1.0.0](https://github.com/pmmp/RakLibIpc/releases/tag/1.0.0).
## Internals
- (Re)Added support for RakLib packet ACK receipts. This was used to throttle resource pack sending and prevent network overloading.
- Added `NetworkSession->sendDataPacketWithReceipt()` to make use of this feature.
- `PacketSender` now requires an additional `?int $receiptId` parameter.
- `ResourcePackPacketHandler` now uses `sendDataPacketWithReceipt()` to send resource packs, and delays sending the next chunk until the current one is acknowledged.
- `ResourcePackPacketHandler` now accepts resource pack info directly in the constructor, instead of `ResourcePackManager`. This eases the implementation of `PlayerResourcePackOfferEvent`.
- Increased `ZlibCompressor::DEFAULT_MAX_DECOMPRESSION_SIZE` to 8 MB (previously 2 MB). While this weakens server security, it appears to be necessary to deal with extremely bloated Persona skins.
- Increased max split packet parts accepted by `RakLib` to 512 (previously 128). Again, this is necessary to deal with extremely bloated Persona skins.
- Added a new cache mechanism for `PocketMine-MP.phar`.
- `ext-phar`'s default mechanism is extremely wasteful (generating a separate cache file per thread), and doesn't clean up after itself.
- The new cache mechanism is shared between all threads, and automatically cleans up stale caches.
- The phar stub (`build/server-phar-stub.php`) now converts the phar contents into a `.tar`, and decompresses all the files into `$TMPDIR/PocketMine-MP-phar-cache.<random>/`.
- `phar://` URIs still work with this system, but `new Phar(__FILE__)` must be replaced by `new PharData(__FILE__)` within PocketMine-MP core code.
- Backtraces from a `phar`'d server will now point to a location in the extracted phar cache, rather than the phar itself.
- `block_factory_consistency_check` test (actually for `RuntimeBlockStateRegistry`) now stores less data, and is no longer affected by changes to internal state ID construction.
# 5.14.1
Released 5th April 2024.
## Fixes
- Fixed incorrect `pmmpthread` version check in server bootstrap.

16
changelogs/5.15.md Normal file
View File

@ -0,0 +1,16 @@
# 5.15.0
Released 25th April 2024.
**For Minecraft: Bedrock Edition 1.20.80**
This is a support release for Minecraft: Bedrock Edition 1.20.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.20.80.
- Removed support for earlier versions.

View File

@ -22,7 +22,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pmmpthread": "^6.0.7",
"ext-pmmpthread": "^6.1.0",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
@ -33,26 +33,26 @@
"composer-runtime-api": "^2.0",
"adhocore/json-comment": "~1.2.0",
"pocketmine/netresearch-jsonmapper": "~v4.4.999",
"pocketmine/bedrock-block-upgrade-schema": "~3.5.0+bedrock-1.20.60",
"pocketmine/bedrock-data": "~2.8.0+bedrock-1.20.60",
"pocketmine/bedrock-item-upgrade-schema": "~1.7.0+bedrock-1.20.60",
"pocketmine/bedrock-protocol": "~27.0.0+bedrock-1.20.60",
"pocketmine/bedrock-block-upgrade-schema": "~4.1.0+bedrock-1.20.80",
"pocketmine/bedrock-data": "~2.10.0+bedrock-1.20.80",
"pocketmine/bedrock-item-upgrade-schema": "~1.9.0+bedrock-1.20.80",
"pocketmine/bedrock-protocol": "~30.0.0+bedrock-1.20.80",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0",
"pocketmine/errorhandler": "^0.6.0",
"pocketmine/errorhandler": "^0.7.0",
"pocketmine/locale-data": "~2.19.0",
"pocketmine/log": "^0.4.0",
"pocketmine/math": "~1.0.0",
"pocketmine/nbt": "~1.0.0",
"pocketmine/raklib": "^0.15.0",
"pocketmine/raklib-ipc": "^0.2.0",
"pocketmine/raklib": "~1.1.0",
"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": "1.10.58",
"phpstan/phpstan": "1.10.66",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "~10.3.0 || ~10.2.0 || ~10.1.0"
@ -83,11 +83,14 @@
"@composer install --no-dev --classmap-authoritative --ignore-platform-reqs",
"@php -dphar.readonly=0 build/server-phar.php"
],
"update-registry-annotations": [
"update-codegen": [
"@php build/generate-bedrockdata-path-consts.php",
"@php build/generate-biome-ids.php",
"@php build/generate-block-serializer-consts.php vendor/pocketmine/bedrock-data/canonical_block_states.nbt",
"@php build/generate-item-type-names.php vendor/pocketmine/bedrock-data/required_item_list.json",
"@php build/generate-known-translation-apis.php",
"@php build/generate-pocketmine-yml-property-consts.php",
"@php build/generate-registry-annotations.php src"
],
"update-translation-apis": [
"@php build/generate-known-translation-apis.php"
]
}
}

320
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": "14848cb7b70d0fa63ed46b30128c2320",
"content-hash": "7cddeede03046e04932d117c76898baf",
"packages": [
{
"name": "adhocore/json-comment",
@ -122,16 +122,16 @@
},
{
"name": "pocketmine/bedrock-block-upgrade-schema",
"version": "3.5.0",
"version": "4.1.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
"reference": "1ed4ba738333c4b4afe4fef8e9326a45c89f12e3"
"reference": "d6b10cb0a5e69fb1dfe3b7f493bf4f519723a9cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/1ed4ba738333c4b4afe4fef8e9326a45c89f12e3",
"reference": "1ed4ba738333c4b4afe4fef8e9326a45c89f12e3",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/d6b10cb0a5e69fb1dfe3b7f493bf4f519723a9cb",
"reference": "d6b10cb0a5e69fb1dfe3b7f493bf4f519723a9cb",
"shasum": ""
},
"type": "library",
@ -142,22 +142,22 @@
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
"support": {
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/3.5.0"
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/4.1.0"
},
"time": "2024-02-07T11:46:50+00:00"
"time": "2024-04-05T17:28:14+00:00"
},
{
"name": "pocketmine/bedrock-data",
"version": "2.8.0+bedrock-1.20.60",
"version": "2.10.0+bedrock-1.20.80",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "d8ea0355b7c835564af9fe6e273e650ac62c84a2"
"reference": "d7d709fec3848f56ca77f5ff0e5d4741b59f9d69"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/d8ea0355b7c835564af9fe6e273e650ac62c84a2",
"reference": "d8ea0355b7c835564af9fe6e273e650ac62c84a2",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/d7d709fec3848f56ca77f5ff0e5d4741b59f9d69",
"reference": "d7d709fec3848f56ca77f5ff0e5d4741b59f9d69",
"shasum": ""
},
"type": "library",
@ -168,22 +168,22 @@
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/BedrockData/issues",
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.20.60"
"source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.20.80"
},
"time": "2024-02-07T11:23:46+00:00"
"time": "2024-04-25T10:08:23+00:00"
},
{
"name": "pocketmine/bedrock-item-upgrade-schema",
"version": "1.7.0",
"version": "1.9.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
"reference": "69772dd58e2b2c7b7513fa2bcdc46e782228641c"
"reference": "24a89457c17c271b5378b195931e720873865a79"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/69772dd58e2b2c7b7513fa2bcdc46e782228641c",
"reference": "69772dd58e2b2c7b7513fa2bcdc46e782228641c",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/24a89457c17c271b5378b195931e720873865a79",
"reference": "24a89457c17c271b5378b195931e720873865a79",
"shasum": ""
},
"type": "library",
@ -194,22 +194,22 @@
"description": "JSON schemas for upgrading items found in older Minecraft: Bedrock world saves",
"support": {
"issues": "https://github.com/pmmp/BedrockItemUpgradeSchema/issues",
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.7.0"
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.9.0"
},
"time": "2024-02-07T11:58:05+00:00"
"time": "2024-04-05T18:46:07+00:00"
},
{
"name": "pocketmine/bedrock-protocol",
"version": "27.0.2+bedrock-1.20.60",
"version": "30.0.0+bedrock-1.20.80",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "6905865133b69da8c95a13c563d349e1993c06b8"
"reference": "dc7606a9f778eeeeccfae393bd58e0b62ec6f85a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/6905865133b69da8c95a13c563d349e1993c06b8",
"reference": "6905865133b69da8c95a13c563d349e1993c06b8",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/dc7606a9f778eeeeccfae393bd58e0b62ec6f85a",
"reference": "dc7606a9f778eeeeccfae393bd58e0b62ec6f85a",
"shasum": ""
},
"require": {
@ -222,7 +222,7 @@
"ramsey/uuid": "^4.1"
},
"require-dev": {
"phpstan/phpstan": "1.10.39",
"phpstan/phpstan": "1.10.59",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5 || ^10.0"
@ -240,22 +240,22 @@
"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/27.0.2+bedrock-1.20.60"
"source": "https://github.com/pmmp/BedrockProtocol/tree/30.0.0+bedrock-1.20.80"
},
"time": "2024-02-23T13:43:39+00:00"
"time": "2024-04-05T17:53:35+00:00"
},
{
"name": "pocketmine/binaryutils",
"version": "0.2.4",
"version": "0.2.6",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BinaryUtils.git",
"reference": "5ac7eea91afbad8dc498f5ce34ce6297d5e6ea9a"
"reference": "ccfc1899b859d45814ea3592e20ebec4cb731c84"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/5ac7eea91afbad8dc498f5ce34ce6297d5e6ea9a",
"reference": "5ac7eea91afbad8dc498f5ce34ce6297d5e6ea9a",
"url": "https://api.github.com/repos/pmmp/BinaryUtils/zipball/ccfc1899b859d45814ea3592e20ebec4cb731c84",
"reference": "ccfc1899b859d45814ea3592e20ebec4cb731c84",
"shasum": ""
},
"require": {
@ -264,10 +264,10 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "1.3.0",
"phpstan/phpstan": "~1.10.3",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0.0",
"phpunit/phpunit": "^9.5"
"phpunit/phpunit": "^9.5 || ^10.0 || ^11.0"
},
"type": "library",
"autoload": {
@ -282,9 +282,9 @@
"description": "Classes and methods for conveniently handling binary data",
"support": {
"issues": "https://github.com/pmmp/BinaryUtils/issues",
"source": "https://github.com/pmmp/BinaryUtils/tree/0.2.4"
"source": "https://github.com/pmmp/BinaryUtils/tree/0.2.6"
},
"time": "2022-01-12T18:06:33+00:00"
"time": "2024-03-04T15:04:17+00:00"
},
{
"name": "pocketmine/callback-validator",
@ -376,25 +376,25 @@
},
{
"name": "pocketmine/errorhandler",
"version": "0.6.0",
"version": "0.7.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/ErrorHandler.git",
"reference": "dae214a04348b911e8219ebf125ff1c5589cc878"
"reference": "cae94884368a74ece5294b9ff7fef18732dcd921"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/ErrorHandler/zipball/dae214a04348b911e8219ebf125ff1c5589cc878",
"reference": "dae214a04348b911e8219ebf125ff1c5589cc878",
"url": "https://api.github.com/repos/pmmp/ErrorHandler/zipball/cae94884368a74ece5294b9ff7fef18732dcd921",
"reference": "cae94884368a74ece5294b9ff7fef18732dcd921",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-strict-rules": "^0.12.2",
"phpunit/phpunit": "^9.5"
"phpstan/phpstan": "~1.10.3",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5 || ^10.0 || ^11.0"
},
"type": "library",
"autoload": {
@ -409,9 +409,9 @@
"description": "Utilities to handle nasty PHP E_* errors in a usable way",
"support": {
"issues": "https://github.com/pmmp/ErrorHandler/issues",
"source": "https://github.com/pmmp/ErrorHandler/tree/0.6.0"
"source": "https://github.com/pmmp/ErrorHandler/tree/0.7.0"
},
"time": "2022-01-08T21:05:46+00:00"
"time": "2024-04-02T18:29:54+00:00"
},
{
"name": "pocketmine/locale-data",
@ -616,28 +616,28 @@
},
{
"name": "pocketmine/raklib",
"version": "0.15.1",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/RakLib.git",
"reference": "79b7b4d1d7516dc6e322514453645ad9452b20ca"
"reference": "be2783be516bf6e2872ff5c81fb9048596617b97"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/79b7b4d1d7516dc6e322514453645ad9452b20ca",
"reference": "79b7b4d1d7516dc6e322514453645ad9452b20ca",
"url": "https://api.github.com/repos/pmmp/RakLib/zipball/be2783be516bf6e2872ff5c81fb9048596617b97",
"reference": "be2783be516bf6e2872ff5c81fb9048596617b97",
"shasum": ""
},
"require": {
"ext-sockets": "*",
"php": "^8.0",
"php": "^8.1",
"php-64bit": "*",
"php-ipv6": "*",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/log": "^0.3.0 || ^0.4.0"
},
"require-dev": {
"phpstan/phpstan": "1.9.17",
"phpstan/phpstan": "1.10.1",
"phpstan/phpstan-strict-rules": "^1.0"
},
"type": "library",
@ -653,32 +653,32 @@
"description": "A RakNet server implementation written in PHP",
"support": {
"issues": "https://github.com/pmmp/RakLib/issues",
"source": "https://github.com/pmmp/RakLib/tree/0.15.1"
"source": "https://github.com/pmmp/RakLib/tree/1.1.1"
},
"time": "2023-03-07T15:10:34+00:00"
"time": "2024-03-04T14:02:14+00:00"
},
{
"name": "pocketmine/raklib-ipc",
"version": "0.2.0",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/pmmp/RakLibIpc.git",
"reference": "26ed56fa9db06e4ca6e8920c0ede2e01e219bb9c"
"reference": "ce632ef2c6743e71eddb5dc329c49af6555f90bc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/26ed56fa9db06e4ca6e8920c0ede2e01e219bb9c",
"reference": "26ed56fa9db06e4ca6e8920c0ede2e01e219bb9c",
"url": "https://api.github.com/repos/pmmp/RakLibIpc/zipball/ce632ef2c6743e71eddb5dc329c49af6555f90bc",
"reference": "ce632ef2c6743e71eddb5dc329c49af6555f90bc",
"shasum": ""
},
"require": {
"php": "^8.0",
"php-64bit": "*",
"pocketmine/binaryutils": "^0.2.0",
"pocketmine/raklib": "^0.15.0"
"pocketmine/raklib": "^0.15.0 || ^1.0.0"
},
"require-dev": {
"phpstan/phpstan": "1.9.17",
"phpstan/phpstan": "1.10.1",
"phpstan/phpstan-strict-rules": "^1.0.0"
},
"type": "library",
@ -694,9 +694,9 @@
"description": "Channel-based protocols for inter-thread/inter-process communication with RakLib",
"support": {
"issues": "https://github.com/pmmp/RakLibIpc/issues",
"source": "https://github.com/pmmp/RakLibIpc/tree/0.2.0"
"source": "https://github.com/pmmp/RakLibIpc/tree/1.0.1"
},
"time": "2023-02-13T13:40:40+00:00"
"time": "2024-03-01T15:55:05+00:00"
},
{
"name": "pocketmine/snooze",
@ -921,16 +921,16 @@
},
{
"name": "symfony/filesystem",
"version": "v6.4.0",
"version": "v6.4.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59"
"reference": "9919b5509ada52cc7f66f9a35c86a4a29955c9d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/952a8cb588c3bc6ce76f6023000fb932f16a6e59",
"reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/9919b5509ada52cc7f66f9a35c86a4a29955c9d3",
"reference": "9919b5509ada52cc7f66f9a35c86a4a29955c9d3",
"shasum": ""
},
"require": {
@ -964,7 +964,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.4.0"
"source": "https://github.com/symfony/filesystem/tree/v6.4.6"
},
"funding": [
{
@ -980,20 +980,20 @@
"type": "tidelift"
}
],
"time": "2023-07-26T17:27:13+00:00"
"time": "2024-03-21T19:36:20+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.28.0",
"version": "v1.29.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
"shasum": ""
},
"require": {
@ -1007,9 +1007,6 @@
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
@ -1046,7 +1043,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
},
"funding": [
{
@ -1062,20 +1059,20 @@
"type": "tidelift"
}
],
"time": "2023-01-26T09:26:14+00:00"
"time": "2024-01-29T20:11:03+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.28.0",
"version": "v1.29.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "42292d99c55abe617799667f454222c54c60e229"
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
"reference": "42292d99c55abe617799667f454222c54c60e229",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
"shasum": ""
},
"require": {
@ -1089,9 +1086,6 @@
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
@ -1129,7 +1123,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
},
"funding": [
{
@ -1145,7 +1139,7 @@
"type": "tidelift"
}
],
"time": "2023-07-28T09:04:16+00:00"
"time": "2024-01-29T20:11:03+00:00"
}
],
"packages-dev": [
@ -1210,16 +1204,16 @@
},
{
"name": "nikic/php-parser",
"version": "v5.0.0",
"version": "v5.0.2",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc"
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc",
"reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13",
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13",
"shasum": ""
},
"require": {
@ -1262,26 +1256,27 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2"
},
"time": "2024-01-07T17:17:35+00:00"
"time": "2024-03-05T20:51:40+00:00"
},
{
"name": "phar-io/manifest",
"version": "2.0.3",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phar-io/manifest.git",
"reference": "97803eca37d319dfa7826cc2437fc020857acb53"
"reference": "54750ef60c58e43759730615a392c31c80e23176"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
"reference": "97803eca37d319dfa7826cc2437fc020857acb53",
"url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
"reference": "54750ef60c58e43759730615a392c31c80e23176",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-phar": "*",
"ext-xmlwriter": "*",
"phar-io/version": "^3.0.1",
@ -1322,9 +1317,15 @@
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": {
"issues": "https://github.com/phar-io/manifest/issues",
"source": "https://github.com/phar-io/manifest/tree/2.0.3"
"source": "https://github.com/phar-io/manifest/tree/2.0.4"
},
"time": "2021-07-20T11:28:43+00:00"
"funding": [
{
"url": "https://github.com/theseer",
"type": "github"
}
],
"time": "2024-03-03T12:33:53+00:00"
},
{
"name": "phar-io/version",
@ -1379,16 +1380,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.58",
"version": "1.10.66",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "a23518379ec4defd9e47cbf81019526861623ec2"
"reference": "94779c987e4ebd620025d9e5fdd23323903950bd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a23518379ec4defd9e47cbf81019526861623ec2",
"reference": "a23518379ec4defd9e47cbf81019526861623ec2",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/94779c987e4ebd620025d9e5fdd23323903950bd",
"reference": "94779c987e4ebd620025d9e5fdd23323903950bd",
"shasum": ""
},
"require": {
@ -1437,20 +1438,20 @@
"type": "tidelift"
}
],
"time": "2024-02-12T20:02:57+00:00"
"time": "2024-03-28T16:17:31+00:00"
},
{
"name": "phpstan/phpstan-phpunit",
"version": "1.3.15",
"version": "1.3.16",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phpunit.git",
"reference": "70ecacc64fe8090d8d2a33db5a51fe8e88acd93a"
"reference": "d5242a59d035e46774f2e634b374bc39ff62cb95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/70ecacc64fe8090d8d2a33db5a51fe8e88acd93a",
"reference": "70ecacc64fe8090d8d2a33db5a51fe8e88acd93a",
"url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d5242a59d035e46774f2e634b374bc39ff62cb95",
"reference": "d5242a59d035e46774f2e634b374bc39ff62cb95",
"shasum": ""
},
"require": {
@ -1487,27 +1488,27 @@
"description": "PHPUnit extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-phpunit/issues",
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.15"
"source": "https://github.com/phpstan/phpstan-phpunit/tree/1.3.16"
},
"time": "2023-10-09T18:58:39+00:00"
"time": "2024-02-23T09:51:20+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "1.5.2",
"version": "1.5.3",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542"
"reference": "568210bd301f94a0d4b1e5a0808c374c1b9cf11b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/7a50e9662ee9f3942e4aaaf3d603653f60282542",
"reference": "7a50e9662ee9f3942e4aaaf3d603653f60282542",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/568210bd301f94a0d4b1e5a0808c374c1b9cf11b",
"reference": "568210bd301f94a0d4b1e5a0808c374c1b9cf11b",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpstan/phpstan": "^1.10.34"
"phpstan/phpstan": "^1.10.60"
},
"require-dev": {
"nikic/php-parser": "^4.13.0",
@ -1536,22 +1537,22 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.2"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.3"
},
"time": "2023-10-30T14:35:06+00:00"
"time": "2024-04-06T07:43:25+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "10.1.11",
"version": "10.1.14",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "78c3b7625965c2513ee96569a4dbb62601784145"
"reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/78c3b7625965c2513ee96569a4dbb62601784145",
"reference": "78c3b7625965c2513ee96569a4dbb62601784145",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e3f51450ebffe8e0efdf7346ae966a656f7d5e5b",
"reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b",
"shasum": ""
},
"require": {
@ -1608,7 +1609,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.11"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.14"
},
"funding": [
{
@ -1616,7 +1617,7 @@
"type": "github"
}
],
"time": "2023-12-21T15:38:30+00:00"
"time": "2024-03-12T15:33:41+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -1964,16 +1965,16 @@
},
{
"name": "sebastian/cli-parser",
"version": "2.0.0",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
"reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae"
"reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae",
"reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae",
"url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084",
"reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084",
"shasum": ""
},
"require": {
@ -2008,7 +2009,8 @@
"homepage": "https://github.com/sebastianbergmann/cli-parser",
"support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues",
"source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0"
"security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
"source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1"
},
"funding": [
{
@ -2016,7 +2018,7 @@
"type": "github"
}
],
"time": "2023-02-03T06:58:15+00:00"
"time": "2024-03-02T07:12:49+00:00"
},
{
"name": "sebastian/code-unit",
@ -2266,16 +2268,16 @@
},
{
"name": "sebastian/diff",
"version": "5.1.0",
"version": "5.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f"
"reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
"reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
"reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
"shasum": ""
},
"require": {
@ -2283,7 +2285,7 @@
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"symfony/process": "^4.2 || ^5"
"symfony/process": "^6.4"
},
"type": "library",
"extra": {
@ -2321,7 +2323,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"security": "https://github.com/sebastianbergmann/diff/security/policy",
"source": "https://github.com/sebastianbergmann/diff/tree/5.1.0"
"source": "https://github.com/sebastianbergmann/diff/tree/5.1.1"
},
"funding": [
{
@ -2329,20 +2331,20 @@
"type": "github"
}
],
"time": "2023-12-22T10:55:06+00:00"
"time": "2024-03-02T07:15:17+00:00"
},
{
"name": "sebastian/environment",
"version": "6.0.1",
"version": "6.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951"
"reference": "8074dbcd93529b357029f5cc5058fd3e43666984"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/43c751b41d74f96cbbd4e07b7aec9675651e2951",
"reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984",
"reference": "8074dbcd93529b357029f5cc5058fd3e43666984",
"shasum": ""
},
"require": {
@ -2357,7 +2359,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "6.0-dev"
"dev-main": "6.1-dev"
}
},
"autoload": {
@ -2385,7 +2387,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"security": "https://github.com/sebastianbergmann/environment/security/policy",
"source": "https://github.com/sebastianbergmann/environment/tree/6.0.1"
"source": "https://github.com/sebastianbergmann/environment/tree/6.1.0"
},
"funding": [
{
@ -2393,20 +2395,20 @@
"type": "github"
}
],
"time": "2023-04-11T05:39:26+00:00"
"time": "2024-03-23T08:47:14+00:00"
},
{
"name": "sebastian/exporter",
"version": "5.1.1",
"version": "5.1.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc"
"reference": "955288482d97c19a372d3f31006ab3f37da47adf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/64f51654862e0f5e318db7e9dcc2292c63cdbddc",
"reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf",
"reference": "955288482d97c19a372d3f31006ab3f37da47adf",
"shasum": ""
},
"require": {
@ -2463,7 +2465,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"security": "https://github.com/sebastianbergmann/exporter/security/policy",
"source": "https://github.com/sebastianbergmann/exporter/tree/5.1.1"
"source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2"
},
"funding": [
{
@ -2471,20 +2473,20 @@
"type": "github"
}
],
"time": "2023-09-24T13:22:09+00:00"
"time": "2024-03-02T07:17:12+00:00"
},
{
"name": "sebastian/global-state",
"version": "6.0.1",
"version": "6.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4"
"reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/7ea9ead78f6d380d2a667864c132c2f7b83055e4",
"reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
"reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
"shasum": ""
},
"require": {
@ -2518,14 +2520,14 @@
}
],
"description": "Snapshotting of global state",
"homepage": "http://www.github.com/sebastianbergmann/global-state",
"homepage": "https://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"security": "https://github.com/sebastianbergmann/global-state/security/policy",
"source": "https://github.com/sebastianbergmann/global-state/tree/6.0.1"
"source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2"
},
"funding": [
{
@ -2533,7 +2535,7 @@
"type": "github"
}
],
"time": "2023-07-19T07:19:23+00:00"
"time": "2024-03-02T07:19:19+00:00"
},
{
"name": "sebastian/lines-of-code",
@ -2879,16 +2881,16 @@
},
{
"name": "theseer/tokenizer",
"version": "1.2.2",
"version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
"shasum": ""
},
"require": {
@ -2917,7 +2919,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/1.2.2"
"source": "https://github.com/theseer/tokenizer/tree/1.2.3"
},
"funding": [
{
@ -2925,7 +2927,7 @@
"type": "github"
}
],
"time": "2023-11-20T00:12:19+00:00"
"time": "2024-03-03T12:36:25+00:00"
}
],
"aliases": [],
@ -2951,7 +2953,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pmmpthread": "^6.0.7",
"ext-pmmpthread": "^6.1.0",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",

View File

@ -45,4 +45,8 @@ final class BootstrapOptions{
public const PLUGINS = "plugins";
/** Path to store and load server data */
public const DATA = "data";
/** Shows basic server version information and exits */
public const VERSION = "version";
/** Disables writing logs to server.log */
public const NO_LOG_FILE = "no-log-file";
}

View File

@ -25,6 +25,7 @@ namespace pocketmine {
use Composer\InstalledVersions;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\thread\ThreadManager;
use pocketmine\thread\ThreadSafeClassLoader;
use pocketmine\utils\Filesystem;
@ -40,14 +41,17 @@ namespace pocketmine {
use function extension_loaded;
use function function_exists;
use function getcwd;
use function getopt;
use function is_dir;
use function mkdir;
use function phpversion;
use function preg_match;
use function preg_quote;
use function printf;
use function realpath;
use function version_compare;
use const DIRECTORY_SEPARATOR;
use const PHP_EOL;
require_once __DIR__ . '/VersionInfo.php';
@ -120,8 +124,8 @@ namespace pocketmine {
}
if(($pmmpthread_version = phpversion("pmmpthread")) !== false){
if(version_compare($pmmpthread_version, "6.0.7") < 0 || version_compare($pmmpthread_version, "7.0.0") >= 0){
$messages[] = "pmmpthread ^6.0.7 is required, while you have $pmmpthread_version.";
if(version_compare($pmmpthread_version, "6.1.0") < 0 || version_compare($pmmpthread_version, "7.0.0") >= 0){
$messages[] = "pmmpthread ^6.1.0 is required, while you have $pmmpthread_version.";
}
}
@ -166,7 +170,7 @@ namespace pocketmine {
* @return void
*/
function emit_performance_warnings(\Logger $logger){
if(PHP_DEBUG !== 0){
if(ZEND_DEBUG_BUILD){
$logger->warning("This PHP binary was compiled in debug mode. This has a major impact on performance.");
}
if(extension_loaded("xdebug") && (!function_exists('xdebug_info') || count(xdebug_info('mode')) !== 0)){
@ -273,6 +277,11 @@ JIT_WARNING
ErrorToExceptionHandler::set();
if(count(getopt("", [BootstrapOptions::VERSION])) > 0){
printf("%s %s (git hash %s) for Minecraft: Bedrock Edition %s\n", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true), VersionInfo::GIT_HASH(), ProtocolInfo::MINECRAFT_VERSION);
exit(0);
}
$cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
$dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd;
$pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
@ -308,7 +317,7 @@ JIT_WARNING
//Logger has a dependency on timezone
Timezone::init();
$opts = getopt("", [BootstrapOptions::NO_WIZARD, BootstrapOptions::ENABLE_ANSI, BootstrapOptions::DISABLE_ANSI]);
$opts = getopt("", [BootstrapOptions::NO_WIZARD, BootstrapOptions::ENABLE_ANSI, BootstrapOptions::DISABLE_ANSI, BootstrapOptions::NO_LOG_FILE]);
if(isset($opts[BootstrapOptions::ENABLE_ANSI])){
Terminal::init(true);
}elseif(isset($opts[BootstrapOptions::DISABLE_ANSI])){
@ -316,8 +325,13 @@ JIT_WARNING
}else{
Terminal::init();
}
$logFile = isset($opts[BootstrapOptions::NO_LOG_FILE]) ? null : Path::join($dataPath, "server.log");
$logger = new MainLogger($logFile, Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()), false, Path::join($dataPath, "log_archive"));
if($logFile === null){
$logger->notice("Logging to file disabled. Ensure logs are collected by other means (e.g. Docker logs).");
}
$logger = new MainLogger(Path::join($dataPath, "server.log"), Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()));
\GlobalLogger::set($logger);
emit_performance_warnings($logger);

View File

@ -59,7 +59,6 @@ use pocketmine\network\mcpe\EntityEventBroadcaster;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\PacketBroadcaster;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
use pocketmine\network\mcpe\raklib\RakLibInterface;
use pocketmine\network\mcpe\StandardEntityEventBroadcaster;
@ -1187,12 +1186,11 @@ class Server{
bool $useQuery,
PacketBroadcaster $packetBroadcaster,
EntityEventBroadcaster $entityEventBroadcaster,
PacketSerializerContext $packetSerializerContext,
TypeConverter $typeConverter
) : bool{
$prettyIp = $ipV6 ? "[$ip]" : $ip;
try{
$rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext, $typeConverter));
$rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $entityEventBroadcaster, $typeConverter));
}catch(NetworkInterfaceStartException $e){
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed(
$ip,
@ -1219,15 +1217,14 @@ class Server{
$useQuery = $this->configGroup->getConfigBool(ServerProperties::ENABLE_QUERY, true);
$typeConverter = TypeConverter::getInstance();
$packetSerializerContext = new PacketSerializerContext($typeConverter->getItemTypeDictionary());
$packetBroadcaster = new StandardPacketBroadcaster($this, $packetSerializerContext);
$packetBroadcaster = new StandardPacketBroadcaster($this);
$entityEventBroadcaster = new StandardEntityEventBroadcaster($packetBroadcaster, $typeConverter);
if(
!$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext, $typeConverter) ||
!$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $typeConverter) ||
(
$this->configGroup->getConfigBool(ServerProperties::ENABLE_IPV6, true) &&
!$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext, $typeConverter)
!$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $typeConverter)
)
){
return false;

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.11.2";
public const BASE_VERSION = "5.15.0";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_CHANNEL = "stable";
@ -63,7 +63,8 @@ final class VersionInfo{
if(\Phar::running(true) === ""){
$gitHash = Git::getRepositoryStatePretty(\pocketmine\PATH);
}else{
$phar = new \Phar(\Phar::running(false));
$pharPath = \Phar::running(false);
$phar = \Phar::isValidPharFilename($pharPath) ? new \Phar($pharPath) : new \PharData($pharPath);
$meta = $phar->getMetadata();
if(isset($meta["git"])){
$gitHash = $meta["git"];
@ -82,7 +83,8 @@ final class VersionInfo{
if(self::$buildNumber === null){
self::$buildNumber = 0;
if(\Phar::running(true) !== ""){
$phar = new \Phar(\Phar::running(false));
$pharPath = \Phar::running(false);
$phar = \Phar::isValidPharFilename($pharPath) ? new \Phar($pharPath) : new \PharData($pharPath);
$meta = $phar->getMetadata();
if(is_array($meta) && isset($meta["build"]) && is_int($meta["build"])){
self::$buildNumber = $meta["build"];

View File

@ -34,7 +34,6 @@ use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function array_filter;
use function assert;
use function count;
@ -89,11 +88,12 @@ abstract class BaseBanner extends Transparent{
* @return $this
*/
public function setPatterns(array $patterns) : self{
$checked = array_filter($patterns, fn($v) => $v instanceof BannerPatternLayer);
if(count($checked) !== count($patterns)){
throw new \TypeError("Deque must only contain " . BannerPatternLayer::class . " objects");
foreach($patterns as $pattern){
if(!$pattern instanceof BannerPatternLayer){
throw new \TypeError("Array must only contain " . BannerPatternLayer::class . " objects");
}
}
$this->patterns = $checked;
$this->patterns = $patterns;
return $this;
}

View File

@ -402,7 +402,7 @@ class Block{
}
/**
* AKA: Block->isPlaceable
* Returns whether this block can be placed when obtained as an item.
*/
public function canBePlaced() : bool{
return true;
@ -572,16 +572,28 @@ class Block{
return $this->getLightFilter() > 0;
}
/**
* Returns whether this block allows any light to pass through it.
*/
public function isTransparent() : bool{
return false;
}
/**
* @deprecated TL;DR: Don't use this function. Its results are confusing and inconsistent.
*
* No one is sure what the meaning of this property actually is. It's borrowed from Minecraft Java Edition, and is
* used by various blocks for support checks.
*
* Things like signs and banners are considered "solid" despite having no collision box, and things like skulls and
* flower pots are considered non-solid despite obviously being "solid" in the conventional, real-world sense.
*/
public function isSolid() : bool{
return true;
}
/**
* AKA: Block->isFlowable
* Returns whether this block can be destroyed by liquid flowing into its cell.
*/
public function canBeFlowedInto() : bool{
return false;

View File

@ -26,7 +26,6 @@ namespace pocketmine\block;
use pocketmine\block\utils\AgeableTrait;
use pocketmine\block\utils\BlockEventHelper;
use pocketmine\block\utils\HorizontalFacingTrait;
use pocketmine\block\utils\SupportType;
use pocketmine\block\utils\WoodType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Fertilizer;
@ -40,7 +39,7 @@ use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use function mt_rand;
class CocoaBlock extends Transparent{
class CocoaBlock extends Flowable{
use HorizontalFacingTrait;
use AgeableTrait;
@ -65,10 +64,6 @@ class CocoaBlock extends Transparent{
];
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE;
}
private function canAttachTo(Block $block) : bool{
return $block instanceof Wood && $block->getWoodType() === WoodType::JUNGLE;
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\block;
use pocketmine\block\tile\Furnace as TileFurnace;
use pocketmine\block\utils\FacesOppositePlacingPlayerTrait;
use pocketmine\block\utils\LightableTrait;
use pocketmine\crafting\FurnaceType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\item\Item;
@ -34,11 +35,10 @@ use function mt_rand;
class Furnace extends Opaque{
use FacesOppositePlacingPlayerTrait;
use LightableTrait;
protected FurnaceType $furnaceType;
protected bool $lit = false;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo, FurnaceType $furnaceType){
$this->furnaceType = $furnaceType;
parent::__construct($idInfo, $name, $typeInfo);
@ -57,18 +57,6 @@ class Furnace extends Opaque{
return $this->lit ? 13 : 0;
}
public function isLit() : bool{
return $this->lit;
}
/**
* @return $this
*/
public function setLit(bool $lit = true) : self{
$this->lit = $lit;
return $this;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($player instanceof Player){
$furnace = $this->position->getWorld()->getTile($this->position);

View File

@ -34,6 +34,7 @@ final class NetherRoots extends Flowable{
$supportBlock = $block->getSide(Facing::DOWN);
return
$supportBlock->hasTypeTag(BlockTypeTags::DIRT) ||
$supportBlock->hasTypeTag(BlockTypeTags::MUD);
$supportBlock->hasTypeTag(BlockTypeTags::MUD) ||
$supportBlock->getTypeId() === BlockTypeIds::SOUL_SOIL;
}
}

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\FortuneDropHelper;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\block\utils\LightableTrait;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\math\Vector3;
@ -32,23 +32,7 @@ use pocketmine\player\Player;
use function mt_rand;
class RedstoneOre extends Opaque{
protected bool $lit = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->bool($this->lit);
}
public function isLit() : bool{
return $this->lit;
}
/**
* @return $this
*/
public function setLit(bool $lit = true) : self{
$this->lit = $lit;
return $this;
}
use LightableTrait;
public function getLightLevel() : int{
return $this->lit ? 9 : 0;

View File

@ -23,28 +23,22 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\LightableTrait;
use pocketmine\data\runtime\RuntimeDataDescriber;
class RedstoneTorch extends Torch{
protected bool $lit = true;
use LightableTrait;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
$this->lit = true;
parent::__construct($idInfo, $name, $typeInfo);
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
parent::describeBlockOnlyState($w);
$w->bool($this->lit);
}
public function isLit() : bool{
return $this->lit;
}
/**
* @return $this
*/
public function setLit(bool $lit = true) : self{
$this->lit = $lit;
return $this;
}
public function getLightLevel() : int{
return $this->lit ? 7 : 0;
}

View File

@ -36,6 +36,7 @@ use pocketmine\item\VanillaItems;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\sound\SweetBerriesPickSound;
use function mt_rand;
class SweetBerryBush extends Flowable{
@ -81,6 +82,7 @@ class SweetBerryBush extends Flowable{
}elseif(($dropAmount = $this->getBerryDropAmount()) > 0){
$world->setBlock($this->position, $this->setAge(self::STAGE_BUSH_NO_BERRIES));
$world->dropItem($this->position, $this->asItem()->setCount($dropAmount));
$world->addSound($this->position, new SweetBerriesPickSound());
}
return true;

View File

@ -82,6 +82,7 @@ enum BannerPatternType{
case DIAGONAL_UP_LEFT;
case DIAGONAL_UP_RIGHT;
case FLOWER;
case GLOBE;
case GRADIENT;
case GRADIENT_UP;
case HALF_HORIZONTAL;
@ -89,6 +90,7 @@ enum BannerPatternType{
case HALF_VERTICAL;
case HALF_VERTICAL_RIGHT;
case MOJANG;
case PIGLIN;
case RHOMBUS;
case SKULL;
case SMALL_STRIPES;

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\block\utils;
use pocketmine\block\Block;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\entity\projectile\Projectile;
use pocketmine\item\Durable;
use pocketmine\item\enchantment\VanillaEnchantments;
@ -38,24 +37,12 @@ use pocketmine\world\sound\FireExtinguishSound;
use pocketmine\world\sound\FlintSteelSound;
trait CandleTrait{
private bool $lit = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->bool($this->lit);
}
use LightableTrait;
public function getLightLevel() : int{
return $this->lit ? 3 : 0;
}
public function isLit() : bool{ return $this->lit; }
/** @return $this */
public function setLit(bool $lit) : self{
$this->lit = $lit;
return $this;
}
/** @see Block::onInteract() */
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($item->getTypeId() === ItemTypeIds::FIRE_CHARGE || $item->getTypeId() === ItemTypeIds::FLINT_AND_STEEL || $item->hasEnchantment(VanillaEnchantments::FIRE_ASPECT())){

View File

@ -0,0 +1,46 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block\utils;
use pocketmine\data\runtime\RuntimeDataDescriber;
trait LightableTrait{
protected bool $lit = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->bool($this->lit);
}
public function isLit() : bool{
return $this->lit;
}
/**
* @return $this
*/
public function setLit(bool $lit = true) : self{
$this->lit = $lit;
return $this;
}
}

View File

@ -63,6 +63,12 @@ use function strpos;
use function substr;
use function zend_version;
use function zlib_encode;
use const E_COMPILE_ERROR;
use const E_CORE_ERROR;
use const E_ERROR;
use const E_PARSE;
use const E_RECOVERABLE_ERROR;
use const E_USER_ERROR;
use const FILE_IGNORE_NEW_LINES;
use const JSON_THROW_ON_ERROR;
use const JSON_UNESCAPED_SLASHES;
@ -85,6 +91,9 @@ class CrashDump{
public const PLUGIN_INVOLVEMENT_DIRECT = "direct";
public const PLUGIN_INVOLVEMENT_INDIRECT = "indirect";
public const FATAL_ERROR_MASK =
E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR;
private CrashDumpData $data;
private string $encodedData;
@ -186,7 +195,7 @@ class CrashDump{
$error = $lastExceptionError;
}else{
$error = error_get_last();
if($error === null){
if($error === null || ($error["type"] & self::FATAL_ERROR_MASK) === 0){
throw new \RuntimeException("Crash error information missing - did something use exit()?");
}
$error["trace"] = Utils::printableTrace(Utils::currentTrace(3)); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump

View File

@ -43,44 +43,50 @@ final class BannerPatternTypeIdMap{
private array $enumToId = [];
public function __construct(){
$this->register("bo", BannerPatternType::BORDER);
$this->register("bri", BannerPatternType::BRICKS);
$this->register("mc", BannerPatternType::CIRCLE);
$this->register("cre", BannerPatternType::CREEPER);
$this->register("cr", BannerPatternType::CROSS);
$this->register("cbo", BannerPatternType::CURLY_BORDER);
$this->register("lud", BannerPatternType::DIAGONAL_LEFT);
$this->register("rd", BannerPatternType::DIAGONAL_RIGHT);
$this->register("ld", BannerPatternType::DIAGONAL_UP_LEFT);
$this->register("rud", BannerPatternType::DIAGONAL_UP_RIGHT);
$this->register("flo", BannerPatternType::FLOWER);
$this->register("gra", BannerPatternType::GRADIENT);
$this->register("gru", BannerPatternType::GRADIENT_UP);
$this->register("hh", BannerPatternType::HALF_HORIZONTAL);
$this->register("hhb", BannerPatternType::HALF_HORIZONTAL_BOTTOM);
$this->register("vh", BannerPatternType::HALF_VERTICAL);
$this->register("vhr", BannerPatternType::HALF_VERTICAL_RIGHT);
$this->register("moj", BannerPatternType::MOJANG);
$this->register("mr", BannerPatternType::RHOMBUS);
$this->register("sku", BannerPatternType::SKULL);
$this->register("ss", BannerPatternType::SMALL_STRIPES);
$this->register("bl", BannerPatternType::SQUARE_BOTTOM_LEFT);
$this->register("br", BannerPatternType::SQUARE_BOTTOM_RIGHT);
$this->register("tl", BannerPatternType::SQUARE_TOP_LEFT);
$this->register("tr", BannerPatternType::SQUARE_TOP_RIGHT);
$this->register("sc", BannerPatternType::STRAIGHT_CROSS);
$this->register("bs", BannerPatternType::STRIPE_BOTTOM);
$this->register("cs", BannerPatternType::STRIPE_CENTER);
$this->register("dls", BannerPatternType::STRIPE_DOWNLEFT);
$this->register("drs", BannerPatternType::STRIPE_DOWNRIGHT);
$this->register("ls", BannerPatternType::STRIPE_LEFT);
$this->register("ms", BannerPatternType::STRIPE_MIDDLE);
$this->register("rs", BannerPatternType::STRIPE_RIGHT);
$this->register("ts", BannerPatternType::STRIPE_TOP);
$this->register("bt", BannerPatternType::TRIANGLE_BOTTOM);
$this->register("tt", BannerPatternType::TRIANGLE_TOP);
$this->register("bts", BannerPatternType::TRIANGLES_BOTTOM);
$this->register("tts", BannerPatternType::TRIANGLES_TOP);
foreach(BannerPatternType::cases() as $case){
$this->register(match($case){
BannerPatternType::BORDER => "bo",
BannerPatternType::BRICKS => "bri",
BannerPatternType::CIRCLE => "mc",
BannerPatternType::CREEPER => "cre",
BannerPatternType::CROSS => "cr",
BannerPatternType::CURLY_BORDER => "cbo",
BannerPatternType::DIAGONAL_LEFT => "lud",
BannerPatternType::DIAGONAL_RIGHT => "rd",
BannerPatternType::DIAGONAL_UP_LEFT => "ld",
BannerPatternType::DIAGONAL_UP_RIGHT => "rud",
BannerPatternType::FLOWER => "flo",
BannerPatternType::GLOBE => "glb",
BannerPatternType::GRADIENT => "gra",
BannerPatternType::GRADIENT_UP => "gru",
BannerPatternType::HALF_HORIZONTAL => "hh",
BannerPatternType::HALF_HORIZONTAL_BOTTOM => "hhb",
BannerPatternType::HALF_VERTICAL => "vh",
BannerPatternType::HALF_VERTICAL_RIGHT => "vhr",
BannerPatternType::MOJANG => "moj",
BannerPatternType::PIGLIN => "pig",
BannerPatternType::RHOMBUS => "mr",
BannerPatternType::SKULL => "sku",
BannerPatternType::SMALL_STRIPES => "ss",
BannerPatternType::SQUARE_BOTTOM_LEFT => "bl",
BannerPatternType::SQUARE_BOTTOM_RIGHT => "br",
BannerPatternType::SQUARE_TOP_LEFT => "tl",
BannerPatternType::SQUARE_TOP_RIGHT => "tr",
BannerPatternType::STRAIGHT_CROSS => "sc",
BannerPatternType::STRIPE_BOTTOM => "bs",
BannerPatternType::STRIPE_CENTER => "cs",
BannerPatternType::STRIPE_DOWNLEFT => "dls",
BannerPatternType::STRIPE_DOWNRIGHT => "drs",
BannerPatternType::STRIPE_LEFT => "ls",
BannerPatternType::STRIPE_MIDDLE => "ms",
BannerPatternType::STRIPE_RIGHT => "rs",
BannerPatternType::STRIPE_TOP => "ts",
BannerPatternType::TRIANGLE_BOTTOM => "bt",
BannerPatternType::TRIANGLE_TOP => "tt",
BannerPatternType::TRIANGLES_BOTTOM => "bts",
BannerPatternType::TRIANGLES_TOP => "tts",
}, $case);
}
}
public function register(string $stringId, BannerPatternType $type) : void{

View File

@ -111,4 +111,15 @@ final class BiomeIds{
public const CRIMSON_FOREST = 179;
public const WARPED_FOREST = 180;
public const BASALT_DELTAS = 181;
public const JAGGED_PEAKS = 182;
public const FROZEN_PEAKS = 183;
public const SNOWY_SLOPES = 184;
public const GROVE = 185;
public const MEADOW = 186;
public const LUSH_CAVES = 187;
public const DRIPSTONE_CAVES = 188;
public const STONY_PEAKS = 189;
public const DEEP_DARK = 190;
public const MANGROVE_SWAMP = 191;
public const CHERRY_GROVE = 192;
}

View File

@ -48,22 +48,28 @@ final class DyeColorIdMap{
private array $enumToItemId = [];
private function __construct(){
$this->register(0, ItemTypeNames::WHITE_DYE, DyeColor::WHITE);
$this->register(1, ItemTypeNames::ORANGE_DYE, DyeColor::ORANGE);
$this->register(2, ItemTypeNames::MAGENTA_DYE, DyeColor::MAGENTA);
$this->register(3, ItemTypeNames::LIGHT_BLUE_DYE, DyeColor::LIGHT_BLUE);
$this->register(4, ItemTypeNames::YELLOW_DYE, DyeColor::YELLOW);
$this->register(5, ItemTypeNames::LIME_DYE, DyeColor::LIME);
$this->register(6, ItemTypeNames::PINK_DYE, DyeColor::PINK);
$this->register(7, ItemTypeNames::GRAY_DYE, DyeColor::GRAY);
$this->register(8, ItemTypeNames::LIGHT_GRAY_DYE, DyeColor::LIGHT_GRAY);
$this->register(9, ItemTypeNames::CYAN_DYE, DyeColor::CYAN);
$this->register(10, ItemTypeNames::PURPLE_DYE, DyeColor::PURPLE);
$this->register(11, ItemTypeNames::BLUE_DYE, DyeColor::BLUE);
$this->register(12, ItemTypeNames::BROWN_DYE, DyeColor::BROWN);
$this->register(13, ItemTypeNames::GREEN_DYE, DyeColor::GREEN);
$this->register(14, ItemTypeNames::RED_DYE, DyeColor::RED);
$this->register(15, ItemTypeNames::BLACK_DYE, DyeColor::BLACK);
foreach(DyeColor::cases() as $case){
[$colorId, $dyeItemId] = match($case){
DyeColor::WHITE => [0, ItemTypeNames::WHITE_DYE],
DyeColor::ORANGE => [1, ItemTypeNames::ORANGE_DYE],
DyeColor::MAGENTA => [2, ItemTypeNames::MAGENTA_DYE],
DyeColor::LIGHT_BLUE => [3, ItemTypeNames::LIGHT_BLUE_DYE],
DyeColor::YELLOW => [4, ItemTypeNames::YELLOW_DYE],
DyeColor::LIME => [5, ItemTypeNames::LIME_DYE],
DyeColor::PINK => [6, ItemTypeNames::PINK_DYE],
DyeColor::GRAY => [7, ItemTypeNames::GRAY_DYE],
DyeColor::LIGHT_GRAY => [8, ItemTypeNames::LIGHT_GRAY_DYE],
DyeColor::CYAN => [9, ItemTypeNames::CYAN_DYE],
DyeColor::PURPLE => [10, ItemTypeNames::PURPLE_DYE],
DyeColor::BLUE => [11, ItemTypeNames::BLUE_DYE],
DyeColor::BROWN => [12, ItemTypeNames::BROWN_DYE],
DyeColor::GREEN => [13, ItemTypeNames::GREEN_DYE],
DyeColor::RED => [14, ItemTypeNames::RED_DYE],
DyeColor::BLACK => [15, ItemTypeNames::BLACK_DYE],
};
$this->register($colorId, $dyeItemId, $case);
}
}
private function register(int $id, string $itemId, DyeColor $color) : void{

View File

@ -32,9 +32,13 @@ final class MedicineTypeIdMap{
use IntSaveIdMapTrait;
private function __construct(){
$this->register(MedicineTypeIds::ANTIDOTE, MedicineType::ANTIDOTE);
$this->register(MedicineTypeIds::ELIXIR, MedicineType::ELIXIR);
$this->register(MedicineTypeIds::EYE_DROPS, MedicineType::EYE_DROPS);
$this->register(MedicineTypeIds::TONIC, MedicineType::TONIC);
foreach(MedicineType::cases() as $case){
$this->register(match($case){
MedicineType::ANTIDOTE => MedicineTypeIds::ANTIDOTE,
MedicineType::ELIXIR => MedicineTypeIds::ELIXIR,
MedicineType::EYE_DROPS => MedicineTypeIds::EYE_DROPS,
MedicineType::TONIC => MedicineTypeIds::TONIC,
}, $case);
}
}
}

View File

@ -32,12 +32,16 @@ final class MobHeadTypeIdMap{
use IntSaveIdMapTrait;
private function __construct(){
$this->register(0, MobHeadType::SKELETON);
$this->register(1, MobHeadType::WITHER_SKELETON);
$this->register(2, MobHeadType::ZOMBIE);
$this->register(3, MobHeadType::PLAYER);
$this->register(4, MobHeadType::CREEPER);
$this->register(5, MobHeadType::DRAGON);
$this->register(6, MobHeadType::PIGLIN);
foreach(MobHeadType::cases() as $case){
$this->register(match($case){
MobHeadType::SKELETON => 0,
MobHeadType::WITHER_SKELETON => 1,
MobHeadType::ZOMBIE => 2,
MobHeadType::PLAYER => 3,
MobHeadType::CREEPER => 4,
MobHeadType::DRAGON => 5,
MobHeadType::PIGLIN => 6,
}, $case);
}
}
}

View File

@ -33,16 +33,20 @@ final class MushroomBlockTypeIdMap{
use IntSaveIdMapTrait;
public function __construct(){
$this->register(LegacyMeta::MUSHROOM_BLOCK_ALL_PORES, MushroomBlockType::PORES);
$this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHWEST_CORNER, MushroomBlockType::CAP_NORTHWEST);
$this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_NORTH_SIDE, MushroomBlockType::CAP_NORTH);
$this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHEAST_CORNER, MushroomBlockType::CAP_NORTHEAST);
$this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_WEST_SIDE, MushroomBlockType::CAP_WEST);
$this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_TOP_ONLY, MushroomBlockType::CAP_MIDDLE);
$this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_EAST_SIDE, MushroomBlockType::CAP_EAST);
$this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHWEST_CORNER, MushroomBlockType::CAP_SOUTHWEST);
$this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTH_SIDE, MushroomBlockType::CAP_SOUTH);
$this->register(LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHEAST_CORNER, MushroomBlockType::CAP_SOUTHEAST);
$this->register(LegacyMeta::MUSHROOM_BLOCK_ALL_CAP, MushroomBlockType::ALL_CAP);
foreach(MushroomBlockType::cases() as $case){
$this->register(match($case){
MushroomBlockType::PORES => LegacyMeta::MUSHROOM_BLOCK_ALL_PORES,
MushroomBlockType::CAP_NORTHWEST => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHWEST_CORNER,
MushroomBlockType::CAP_NORTH => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTH_SIDE,
MushroomBlockType::CAP_NORTHEAST => LegacyMeta::MUSHROOM_BLOCK_CAP_NORTHEAST_CORNER,
MushroomBlockType::CAP_WEST => LegacyMeta::MUSHROOM_BLOCK_CAP_WEST_SIDE,
MushroomBlockType::CAP_MIDDLE => LegacyMeta::MUSHROOM_BLOCK_CAP_TOP_ONLY,
MushroomBlockType::CAP_EAST => LegacyMeta::MUSHROOM_BLOCK_CAP_EAST_SIDE,
MushroomBlockType::CAP_SOUTHWEST => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHWEST_CORNER,
MushroomBlockType::CAP_SOUTH => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTH_SIDE,
MushroomBlockType::CAP_SOUTHEAST => LegacyMeta::MUSHROOM_BLOCK_CAP_SOUTHEAST_CORNER,
MushroomBlockType::ALL_CAP => LegacyMeta::MUSHROOM_BLOCK_ALL_CAP,
}, $case);
}
}
}

View File

@ -32,21 +32,25 @@ final class NoteInstrumentIdMap{
use IntSaveIdMapTrait;
private function __construct(){
$this->register(0, NoteInstrument::PIANO);
$this->register(1, NoteInstrument::BASS_DRUM);
$this->register(2, NoteInstrument::SNARE);
$this->register(3, NoteInstrument::CLICKS_AND_STICKS);
$this->register(4, NoteInstrument::DOUBLE_BASS);
$this->register(5, NoteInstrument::BELL);
$this->register(6, NoteInstrument::FLUTE);
$this->register(7, NoteInstrument::CHIME);
$this->register(8, NoteInstrument::GUITAR);
$this->register(9, NoteInstrument::XYLOPHONE);
$this->register(10, NoteInstrument::IRON_XYLOPHONE);
$this->register(11, NoteInstrument::COW_BELL);
$this->register(12, NoteInstrument::DIDGERIDOO);
$this->register(13, NoteInstrument::BIT);
$this->register(14, NoteInstrument::BANJO);
$this->register(15, NoteInstrument::PLING);
foreach(NoteInstrument::cases() as $case){
$this->register(match($case){
NoteInstrument::PIANO => 0,
NoteInstrument::BASS_DRUM => 1,
NoteInstrument::SNARE => 2,
NoteInstrument::CLICKS_AND_STICKS => 3,
NoteInstrument::DOUBLE_BASS => 4,
NoteInstrument::BELL => 5,
NoteInstrument::FLUTE => 6,
NoteInstrument::CHIME => 7,
NoteInstrument::GUITAR => 8,
NoteInstrument::XYLOPHONE => 9,
NoteInstrument::IRON_XYLOPHONE => 10,
NoteInstrument::COW_BELL => 11,
NoteInstrument::DIDGERIDOO => 12,
NoteInstrument::BIT => 13,
NoteInstrument::BANJO => 14,
NoteInstrument::PLING => 15,
}, $case);
}
}
}

View File

@ -32,48 +32,52 @@ final class PotionTypeIdMap{
use IntSaveIdMapTrait;
private function __construct(){
$this->register(PotionTypeIds::WATER, PotionType::WATER);
$this->register(PotionTypeIds::MUNDANE, PotionType::MUNDANE);
$this->register(PotionTypeIds::LONG_MUNDANE, PotionType::LONG_MUNDANE);
$this->register(PotionTypeIds::THICK, PotionType::THICK);
$this->register(PotionTypeIds::AWKWARD, PotionType::AWKWARD);
$this->register(PotionTypeIds::NIGHT_VISION, PotionType::NIGHT_VISION);
$this->register(PotionTypeIds::LONG_NIGHT_VISION, PotionType::LONG_NIGHT_VISION);
$this->register(PotionTypeIds::INVISIBILITY, PotionType::INVISIBILITY);
$this->register(PotionTypeIds::LONG_INVISIBILITY, PotionType::LONG_INVISIBILITY);
$this->register(PotionTypeIds::LEAPING, PotionType::LEAPING);
$this->register(PotionTypeIds::LONG_LEAPING, PotionType::LONG_LEAPING);
$this->register(PotionTypeIds::STRONG_LEAPING, PotionType::STRONG_LEAPING);
$this->register(PotionTypeIds::FIRE_RESISTANCE, PotionType::FIRE_RESISTANCE);
$this->register(PotionTypeIds::LONG_FIRE_RESISTANCE, PotionType::LONG_FIRE_RESISTANCE);
$this->register(PotionTypeIds::SWIFTNESS, PotionType::SWIFTNESS);
$this->register(PotionTypeIds::LONG_SWIFTNESS, PotionType::LONG_SWIFTNESS);
$this->register(PotionTypeIds::STRONG_SWIFTNESS, PotionType::STRONG_SWIFTNESS);
$this->register(PotionTypeIds::SLOWNESS, PotionType::SLOWNESS);
$this->register(PotionTypeIds::LONG_SLOWNESS, PotionType::LONG_SLOWNESS);
$this->register(PotionTypeIds::WATER_BREATHING, PotionType::WATER_BREATHING);
$this->register(PotionTypeIds::LONG_WATER_BREATHING, PotionType::LONG_WATER_BREATHING);
$this->register(PotionTypeIds::HEALING, PotionType::HEALING);
$this->register(PotionTypeIds::STRONG_HEALING, PotionType::STRONG_HEALING);
$this->register(PotionTypeIds::HARMING, PotionType::HARMING);
$this->register(PotionTypeIds::STRONG_HARMING, PotionType::STRONG_HARMING);
$this->register(PotionTypeIds::POISON, PotionType::POISON);
$this->register(PotionTypeIds::LONG_POISON, PotionType::LONG_POISON);
$this->register(PotionTypeIds::STRONG_POISON, PotionType::STRONG_POISON);
$this->register(PotionTypeIds::REGENERATION, PotionType::REGENERATION);
$this->register(PotionTypeIds::LONG_REGENERATION, PotionType::LONG_REGENERATION);
$this->register(PotionTypeIds::STRONG_REGENERATION, PotionType::STRONG_REGENERATION);
$this->register(PotionTypeIds::STRENGTH, PotionType::STRENGTH);
$this->register(PotionTypeIds::LONG_STRENGTH, PotionType::LONG_STRENGTH);
$this->register(PotionTypeIds::STRONG_STRENGTH, PotionType::STRONG_STRENGTH);
$this->register(PotionTypeIds::WEAKNESS, PotionType::WEAKNESS);
$this->register(PotionTypeIds::LONG_WEAKNESS, PotionType::LONG_WEAKNESS);
$this->register(PotionTypeIds::WITHER, PotionType::WITHER);
$this->register(PotionTypeIds::TURTLE_MASTER, PotionType::TURTLE_MASTER);
$this->register(PotionTypeIds::LONG_TURTLE_MASTER, PotionType::LONG_TURTLE_MASTER);
$this->register(PotionTypeIds::STRONG_TURTLE_MASTER, PotionType::STRONG_TURTLE_MASTER);
$this->register(PotionTypeIds::SLOW_FALLING, PotionType::SLOW_FALLING);
$this->register(PotionTypeIds::LONG_SLOW_FALLING, PotionType::LONG_SLOW_FALLING);
$this->register(PotionTypeIds::STRONG_SLOWNESS, PotionType::STRONG_SLOWNESS);
foreach(PotionType::cases() as $case){
$this->register(match($case){
PotionType::WATER => PotionTypeIds::WATER,
PotionType::MUNDANE => PotionTypeIds::MUNDANE,
PotionType::LONG_MUNDANE => PotionTypeIds::LONG_MUNDANE,
PotionType::THICK => PotionTypeIds::THICK,
PotionType::AWKWARD => PotionTypeIds::AWKWARD,
PotionType::NIGHT_VISION => PotionTypeIds::NIGHT_VISION,
PotionType::LONG_NIGHT_VISION => PotionTypeIds::LONG_NIGHT_VISION,
PotionType::INVISIBILITY => PotionTypeIds::INVISIBILITY,
PotionType::LONG_INVISIBILITY => PotionTypeIds::LONG_INVISIBILITY,
PotionType::LEAPING => PotionTypeIds::LEAPING,
PotionType::LONG_LEAPING => PotionTypeIds::LONG_LEAPING,
PotionType::STRONG_LEAPING => PotionTypeIds::STRONG_LEAPING,
PotionType::FIRE_RESISTANCE => PotionTypeIds::FIRE_RESISTANCE,
PotionType::LONG_FIRE_RESISTANCE => PotionTypeIds::LONG_FIRE_RESISTANCE,
PotionType::SWIFTNESS => PotionTypeIds::SWIFTNESS,
PotionType::LONG_SWIFTNESS => PotionTypeIds::LONG_SWIFTNESS,
PotionType::STRONG_SWIFTNESS => PotionTypeIds::STRONG_SWIFTNESS,
PotionType::SLOWNESS => PotionTypeIds::SLOWNESS,
PotionType::LONG_SLOWNESS => PotionTypeIds::LONG_SLOWNESS,
PotionType::WATER_BREATHING => PotionTypeIds::WATER_BREATHING,
PotionType::LONG_WATER_BREATHING => PotionTypeIds::LONG_WATER_BREATHING,
PotionType::HEALING => PotionTypeIds::HEALING,
PotionType::STRONG_HEALING => PotionTypeIds::STRONG_HEALING,
PotionType::HARMING => PotionTypeIds::HARMING,
PotionType::STRONG_HARMING => PotionTypeIds::STRONG_HARMING,
PotionType::POISON => PotionTypeIds::POISON,
PotionType::LONG_POISON => PotionTypeIds::LONG_POISON,
PotionType::STRONG_POISON => PotionTypeIds::STRONG_POISON,
PotionType::REGENERATION => PotionTypeIds::REGENERATION,
PotionType::LONG_REGENERATION => PotionTypeIds::LONG_REGENERATION,
PotionType::STRONG_REGENERATION => PotionTypeIds::STRONG_REGENERATION,
PotionType::STRENGTH => PotionTypeIds::STRENGTH,
PotionType::LONG_STRENGTH => PotionTypeIds::LONG_STRENGTH,
PotionType::STRONG_STRENGTH => PotionTypeIds::STRONG_STRENGTH,
PotionType::WEAKNESS => PotionTypeIds::WEAKNESS,
PotionType::LONG_WEAKNESS => PotionTypeIds::LONG_WEAKNESS,
PotionType::WITHER => PotionTypeIds::WITHER,
PotionType::TURTLE_MASTER => PotionTypeIds::TURTLE_MASTER,
PotionType::LONG_TURTLE_MASTER => PotionTypeIds::LONG_TURTLE_MASTER,
PotionType::STRONG_TURTLE_MASTER => PotionTypeIds::STRONG_TURTLE_MASTER,
PotionType::SLOW_FALLING => PotionTypeIds::SLOW_FALLING,
PotionType::LONG_SLOW_FALLING => PotionTypeIds::LONG_SLOW_FALLING,
PotionType::STRONG_SLOWNESS => PotionTypeIds::STRONG_SLOWNESS,
}, $case);
}
}
}

View File

@ -32,15 +32,20 @@ final class SuspiciousStewTypeIdMap{
use IntSaveIdMapTrait;
private function __construct(){
$this->register(SuspiciousStewTypeIds::POPPY, SuspiciousStewType::POPPY);
$this->register(SuspiciousStewTypeIds::CORNFLOWER, SuspiciousStewType::CORNFLOWER);
$this->register(SuspiciousStewTypeIds::TULIP, SuspiciousStewType::TULIP);
$this->register(SuspiciousStewTypeIds::AZURE_BLUET, SuspiciousStewType::AZURE_BLUET);
$this->register(SuspiciousStewTypeIds::LILY_OF_THE_VALLEY, SuspiciousStewType::LILY_OF_THE_VALLEY);
$this->register(SuspiciousStewTypeIds::DANDELION, SuspiciousStewType::DANDELION);
$this->register(SuspiciousStewTypeIds::BLUE_ORCHID, SuspiciousStewType::BLUE_ORCHID);
$this->register(SuspiciousStewTypeIds::ALLIUM, SuspiciousStewType::ALLIUM);
$this->register(SuspiciousStewTypeIds::OXEYE_DAISY, SuspiciousStewType::OXEYE_DAISY);
$this->register(SuspiciousStewTypeIds::WITHER_ROSE, SuspiciousStewType::WITHER_ROSE);
foreach(SuspiciousStewType::cases() as $case){
$this->register(match($case){
SuspiciousStewType::POPPY => SuspiciousStewTypeIds::POPPY,
SuspiciousStewType::CORNFLOWER => SuspiciousStewTypeIds::CORNFLOWER,
SuspiciousStewType::TULIP => SuspiciousStewTypeIds::TULIP,
SuspiciousStewType::AZURE_BLUET => SuspiciousStewTypeIds::AZURE_BLUET,
SuspiciousStewType::LILY_OF_THE_VALLEY => SuspiciousStewTypeIds::LILY_OF_THE_VALLEY,
SuspiciousStewType::DANDELION => SuspiciousStewTypeIds::DANDELION,
SuspiciousStewType::BLUE_ORCHID => SuspiciousStewTypeIds::BLUE_ORCHID,
SuspiciousStewType::ALLIUM => SuspiciousStewTypeIds::ALLIUM,
SuspiciousStewType::OXEYE_DAISY => SuspiciousStewTypeIds::OXEYE_DAISY,
SuspiciousStewType::WITHER_ROSE => SuspiciousStewTypeIds::WITHER_ROSE,
}, $case);
}
}
}

View File

@ -42,8 +42,8 @@ final class BlockStateData{
public const CURRENT_VERSION =
(1 << 24) | //major
(20 << 16) | //minor
(60 << 8) | //patch
(1); //revision
(80 << 8) | //patch
(3); //revision
public const TAG_NAME = "name";
public const TAG_STATES = "states";

View File

@ -81,7 +81,6 @@ final class BlockStateNames{
public const EXTINGUISHED = "extinguished";
public const FACING_DIRECTION = "facing_direction";
public const FILL_LEVEL = "fill_level";
public const FLOWER_TYPE = "flower_type";
public const GROUND_SIGN_DIRECTION = "ground_sign_direction";
public const GROWING_PLANT_AGE = "growing_plant_age";
public const GROWTH = "growth";
@ -105,9 +104,7 @@ final class BlockStateNames{
public const MOISTURIZED_AMOUNT = "moisturized_amount";
public const MONSTER_EGG_STONE_TYPE = "monster_egg_stone_type";
public const MULTI_FACE_DIRECTION_BITS = "multi_face_direction_bits";
public const NEW_LEAF_TYPE = "new_leaf_type";
public const OCCUPIED_BIT = "occupied_bit";
public const OLD_LEAF_TYPE = "old_leaf_type";
public const OPEN_BIT = "open_bit";
public const ORIENTATION = "orientation";
public const OUTPUT_LIT_BIT = "output_lit_bit";
@ -126,7 +123,6 @@ final class BlockStateNames{
public const ROTATION = "rotation";
public const SAND_STONE_TYPE = "sand_stone_type";
public const SAND_TYPE = "sand_type";
public const SAPLING_TYPE = "sapling_type";
public const SCULK_SENSOR_PHASE = "sculk_sensor_phase";
public const SEA_GRASS_TYPE = "sea_grass_type";
public const SPONGE_TYPE = "sponge_type";
@ -151,6 +147,7 @@ final class BlockStateNames{
public const UPDATE_BIT = "update_bit";
public const UPPER_BLOCK_BIT = "upper_block_bit";
public const UPSIDE_DOWN_BIT = "upside_down_bit";
public const VAULT_STATE = "vault_state";
public const VINE_DIRECTION_BITS = "vine_direction_bits";
public const WALL_BLOCK_TYPE = "wall_block_type";
public const WALL_CONNECTION_TYPE_EAST = "wall_connection_type_east";
@ -160,5 +157,4 @@ final class BlockStateNames{
public const WALL_POST_BIT = "wall_post_bit";
public const WEEPING_VINES_AGE = "weeping_vines_age";
public const WEIRDO_DIRECTION = "weirdo_direction";
public const WOOD_TYPE = "wood_type";
}

View File

@ -93,18 +93,6 @@ final class BlockStateStringValues{
public const DRIPSTONE_THICKNESS_MIDDLE = "middle";
public const DRIPSTONE_THICKNESS_TIP = "tip";
public const FLOWER_TYPE_ALLIUM = "allium";
public const FLOWER_TYPE_CORNFLOWER = "cornflower";
public const FLOWER_TYPE_HOUSTONIA = "houstonia";
public const FLOWER_TYPE_LILY_OF_THE_VALLEY = "lily_of_the_valley";
public const FLOWER_TYPE_ORCHID = "orchid";
public const FLOWER_TYPE_OXEYE = "oxeye";
public const FLOWER_TYPE_POPPY = "poppy";
public const FLOWER_TYPE_TULIP_ORANGE = "tulip_orange";
public const FLOWER_TYPE_TULIP_PINK = "tulip_pink";
public const FLOWER_TYPE_TULIP_RED = "tulip_red";
public const FLOWER_TYPE_TULIP_WHITE = "tulip_white";
public const LEVER_DIRECTION_DOWN_EAST_WEST = "down_east_west";
public const LEVER_DIRECTION_DOWN_NORTH_SOUTH = "down_north_south";
public const LEVER_DIRECTION_EAST = "east";
@ -143,14 +131,6 @@ final class BlockStateStringValues{
public const MONSTER_EGG_STONE_TYPE_STONE = "stone";
public const MONSTER_EGG_STONE_TYPE_STONE_BRICK = "stone_brick";
public const NEW_LEAF_TYPE_ACACIA = "acacia";
public const NEW_LEAF_TYPE_DARK_OAK = "dark_oak";
public const OLD_LEAF_TYPE_BIRCH = "birch";
public const OLD_LEAF_TYPE_JUNGLE = "jungle";
public const OLD_LEAF_TYPE_OAK = "oak";
public const OLD_LEAF_TYPE_SPRUCE = "spruce";
public const ORIENTATION_DOWN_EAST = "down_east";
public const ORIENTATION_DOWN_NORTH = "down_north";
public const ORIENTATION_DOWN_SOUTH = "down_south";
@ -184,13 +164,6 @@ final class BlockStateStringValues{
public const SAND_TYPE_NORMAL = "normal";
public const SAND_TYPE_RED = "red";
public const SAPLING_TYPE_ACACIA = "acacia";
public const SAPLING_TYPE_BIRCH = "birch";
public const SAPLING_TYPE_DARK_OAK = "dark_oak";
public const SAPLING_TYPE_JUNGLE = "jungle";
public const SAPLING_TYPE_OAK = "oak";
public const SAPLING_TYPE_SPRUCE = "spruce";
public const SEA_GRASS_TYPE_DEFAULT = "default";
public const SEA_GRASS_TYPE_DOUBLE_BOT = "double_bot";
public const SEA_GRASS_TYPE_DOUBLE_TOP = "double_top";
@ -264,6 +237,11 @@ final class BlockStateStringValues{
public const TURTLE_EGG_COUNT_THREE_EGG = "three_egg";
public const TURTLE_EGG_COUNT_TWO_EGG = "two_egg";
public const VAULT_STATE_ACTIVE = "active";
public const VAULT_STATE_EJECTING = "ejecting";
public const VAULT_STATE_INACTIVE = "inactive";
public const VAULT_STATE_UNLOCKING = "unlocking";
public const WALL_BLOCK_TYPE_ANDESITE = "andesite";
public const WALL_BLOCK_TYPE_BRICK = "brick";
public const WALL_BLOCK_TYPE_COBBLESTONE = "cobblestone";
@ -295,11 +273,4 @@ final class BlockStateStringValues{
public const WALL_CONNECTION_TYPE_WEST_SHORT = "short";
public const WALL_CONNECTION_TYPE_WEST_TALL = "tall";
public const WOOD_TYPE_ACACIA = "acacia";
public const WOOD_TYPE_BIRCH = "birch";
public const WOOD_TYPE_DARK_OAK = "dark_oak";
public const WOOD_TYPE_JUNGLE = "jungle";
public const WOOD_TYPE_OAK = "oak";
public const WOOD_TYPE_SPRUCE = "spruce";
}

View File

@ -33,18 +33,24 @@ final class BlockTypeNames{
public const ACACIA_BUTTON = "minecraft:acacia_button";
public const ACACIA_DOOR = "minecraft:acacia_door";
public const ACACIA_DOUBLE_SLAB = "minecraft:acacia_double_slab";
public const ACACIA_FENCE = "minecraft:acacia_fence";
public const ACACIA_FENCE_GATE = "minecraft:acacia_fence_gate";
public const ACACIA_HANGING_SIGN = "minecraft:acacia_hanging_sign";
public const ACACIA_LEAVES = "minecraft:acacia_leaves";
public const ACACIA_LOG = "minecraft:acacia_log";
public const ACACIA_PLANKS = "minecraft:acacia_planks";
public const ACACIA_PRESSURE_PLATE = "minecraft:acacia_pressure_plate";
public const ACACIA_SAPLING = "minecraft:acacia_sapling";
public const ACACIA_SLAB = "minecraft:acacia_slab";
public const ACACIA_STAIRS = "minecraft:acacia_stairs";
public const ACACIA_STANDING_SIGN = "minecraft:acacia_standing_sign";
public const ACACIA_TRAPDOOR = "minecraft:acacia_trapdoor";
public const ACACIA_WALL_SIGN = "minecraft:acacia_wall_sign";
public const ACACIA_WOOD = "minecraft:acacia_wood";
public const ACTIVATOR_RAIL = "minecraft:activator_rail";
public const AIR = "minecraft:air";
public const ALLIUM = "minecraft:allium";
public const ALLOW = "minecraft:allow";
public const AMETHYST_BLOCK = "minecraft:amethyst_block";
public const AMETHYST_CLUSTER = "minecraft:amethyst_cluster";
@ -55,6 +61,7 @@ final class BlockTypeNames{
public const AZALEA = "minecraft:azalea";
public const AZALEA_LEAVES = "minecraft:azalea_leaves";
public const AZALEA_LEAVES_FLOWERED = "minecraft:azalea_leaves_flowered";
public const AZURE_BLUET = "minecraft:azure_bluet";
public const BAMBOO = "minecraft:bamboo";
public const BAMBOO_BLOCK = "minecraft:bamboo_block";
public const BAMBOO_BUTTON = "minecraft:bamboo_button";
@ -88,16 +95,21 @@ final class BlockTypeNames{
public const BIG_DRIPLEAF = "minecraft:big_dripleaf";
public const BIRCH_BUTTON = "minecraft:birch_button";
public const BIRCH_DOOR = "minecraft:birch_door";
public const BIRCH_DOUBLE_SLAB = "minecraft:birch_double_slab";
public const BIRCH_FENCE = "minecraft:birch_fence";
public const BIRCH_FENCE_GATE = "minecraft:birch_fence_gate";
public const BIRCH_HANGING_SIGN = "minecraft:birch_hanging_sign";
public const BIRCH_LEAVES = "minecraft:birch_leaves";
public const BIRCH_LOG = "minecraft:birch_log";
public const BIRCH_PLANKS = "minecraft:birch_planks";
public const BIRCH_PRESSURE_PLATE = "minecraft:birch_pressure_plate";
public const BIRCH_SAPLING = "minecraft:birch_sapling";
public const BIRCH_SLAB = "minecraft:birch_slab";
public const BIRCH_STAIRS = "minecraft:birch_stairs";
public const BIRCH_STANDING_SIGN = "minecraft:birch_standing_sign";
public const BIRCH_TRAPDOOR = "minecraft:birch_trapdoor";
public const BIRCH_WALL_SIGN = "minecraft:birch_wall_sign";
public const BIRCH_WOOD = "minecraft:birch_wood";
public const BLACK_CANDLE = "minecraft:black_candle";
public const BLACK_CANDLE_CAKE = "minecraft:black_candle_cake";
public const BLACK_CARPET = "minecraft:black_carpet";
@ -122,6 +134,7 @@ final class BlockTypeNames{
public const BLUE_CONCRETE_POWDER = "minecraft:blue_concrete_powder";
public const BLUE_GLAZED_TERRACOTTA = "minecraft:blue_glazed_terracotta";
public const BLUE_ICE = "minecraft:blue_ice";
public const BLUE_ORCHID = "minecraft:blue_orchid";
public const BLUE_SHULKER_BOX = "minecraft:blue_shulker_box";
public const BLUE_STAINED_GLASS = "minecraft:blue_stained_glass";
public const BLUE_STAINED_GLASS_PANE = "minecraft:blue_stained_glass_pane";
@ -131,6 +144,7 @@ final class BlockTypeNames{
public const BOOKSHELF = "minecraft:bookshelf";
public const BORDER_BLOCK = "minecraft:border_block";
public const BRAIN_CORAL = "minecraft:brain_coral";
public const BRAIN_CORAL_FAN = "minecraft:brain_coral_fan";
public const BREWING_STAND = "minecraft:brewing_stand";
public const BRICK_BLOCK = "minecraft:brick_block";
public const BRICK_STAIRS = "minecraft:brick_stairs";
@ -149,6 +163,7 @@ final class BlockTypeNames{
public const BROWN_WOOL = "minecraft:brown_wool";
public const BUBBLE_COLUMN = "minecraft:bubble_column";
public const BUBBLE_CORAL = "minecraft:bubble_coral";
public const BUBBLE_CORAL_FAN = "minecraft:bubble_coral_fan";
public const BUDDING_AMETHYST = "minecraft:budding_amethyst";
public const CACTUS = "minecraft:cactus";
public const CAKE = "minecraft:cake";
@ -220,11 +235,10 @@ final class BlockTypeNames{
public const COPPER_ORE = "minecraft:copper_ore";
public const COPPER_TRAPDOOR = "minecraft:copper_trapdoor";
public const CORAL_BLOCK = "minecraft:coral_block";
public const CORAL_FAN = "minecraft:coral_fan";
public const CORAL_FAN_DEAD = "minecraft:coral_fan_dead";
public const CORAL_FAN_HANG = "minecraft:coral_fan_hang";
public const CORAL_FAN_HANG2 = "minecraft:coral_fan_hang2";
public const CORAL_FAN_HANG3 = "minecraft:coral_fan_hang3";
public const CORNFLOWER = "minecraft:cornflower";
public const CRACKED_DEEPSLATE_BRICKS = "minecraft:cracked_deepslate_bricks";
public const CRACKED_DEEPSLATE_TILES = "minecraft:cracked_deepslate_tiles";
public const CRACKED_NETHER_BRICKS = "minecraft:cracked_nether_bricks";
@ -266,24 +280,34 @@ final class BlockTypeNames{
public const CYAN_WOOL = "minecraft:cyan_wool";
public const DARK_OAK_BUTTON = "minecraft:dark_oak_button";
public const DARK_OAK_DOOR = "minecraft:dark_oak_door";
public const DARK_OAK_DOUBLE_SLAB = "minecraft:dark_oak_double_slab";
public const DARK_OAK_FENCE = "minecraft:dark_oak_fence";
public const DARK_OAK_FENCE_GATE = "minecraft:dark_oak_fence_gate";
public const DARK_OAK_HANGING_SIGN = "minecraft:dark_oak_hanging_sign";
public const DARK_OAK_LEAVES = "minecraft:dark_oak_leaves";
public const DARK_OAK_LOG = "minecraft:dark_oak_log";
public const DARK_OAK_PLANKS = "minecraft:dark_oak_planks";
public const DARK_OAK_PRESSURE_PLATE = "minecraft:dark_oak_pressure_plate";
public const DARK_OAK_SAPLING = "minecraft:dark_oak_sapling";
public const DARK_OAK_SLAB = "minecraft:dark_oak_slab";
public const DARK_OAK_STAIRS = "minecraft:dark_oak_stairs";
public const DARK_OAK_TRAPDOOR = "minecraft:dark_oak_trapdoor";
public const DARK_OAK_WOOD = "minecraft:dark_oak_wood";
public const DARK_PRISMARINE_STAIRS = "minecraft:dark_prismarine_stairs";
public const DARKOAK_STANDING_SIGN = "minecraft:darkoak_standing_sign";
public const DARKOAK_WALL_SIGN = "minecraft:darkoak_wall_sign";
public const DAYLIGHT_DETECTOR = "minecraft:daylight_detector";
public const DAYLIGHT_DETECTOR_INVERTED = "minecraft:daylight_detector_inverted";
public const DEAD_BRAIN_CORAL = "minecraft:dead_brain_coral";
public const DEAD_BRAIN_CORAL_FAN = "minecraft:dead_brain_coral_fan";
public const DEAD_BUBBLE_CORAL = "minecraft:dead_bubble_coral";
public const DEAD_BUBBLE_CORAL_FAN = "minecraft:dead_bubble_coral_fan";
public const DEAD_FIRE_CORAL = "minecraft:dead_fire_coral";
public const DEAD_FIRE_CORAL_FAN = "minecraft:dead_fire_coral_fan";
public const DEAD_HORN_CORAL = "minecraft:dead_horn_coral";
public const DEAD_HORN_CORAL_FAN = "minecraft:dead_horn_coral_fan";
public const DEAD_TUBE_CORAL = "minecraft:dead_tube_coral";
public const DEAD_TUBE_CORAL_FAN = "minecraft:dead_tube_coral_fan";
public const DEADBUSH = "minecraft:deadbush";
public const DECORATED_POT = "minecraft:decorated_pot";
public const DEEPSLATE = "minecraft:deepslate";
@ -320,7 +344,6 @@ final class BlockTypeNames{
public const DOUBLE_STONE_BLOCK_SLAB2 = "minecraft:double_stone_block_slab2";
public const DOUBLE_STONE_BLOCK_SLAB3 = "minecraft:double_stone_block_slab3";
public const DOUBLE_STONE_BLOCK_SLAB4 = "minecraft:double_stone_block_slab4";
public const DOUBLE_WOODEN_SLAB = "minecraft:double_wooden_slab";
public const DRAGON_EGG = "minecraft:dragon_egg";
public const DRIED_KELP_BLOCK = "minecraft:dried_kelp_block";
public const DRIPSTONE_BLOCK = "minecraft:dripstone_block";
@ -469,6 +492,7 @@ final class BlockTypeNames{
public const FENCE_GATE = "minecraft:fence_gate";
public const FIRE = "minecraft:fire";
public const FIRE_CORAL = "minecraft:fire_coral";
public const FIRE_CORAL_FAN = "minecraft:fire_coral_fan";
public const FLETCHING_TABLE = "minecraft:fletching_table";
public const FLOWER_POT = "minecraft:flower_pot";
public const FLOWERING_AZALEA = "minecraft:flowering_azalea";
@ -490,7 +514,7 @@ final class BlockTypeNames{
public const GOLDEN_RAIL = "minecraft:golden_rail";
public const GRANITE = "minecraft:granite";
public const GRANITE_STAIRS = "minecraft:granite_stairs";
public const GRASS = "minecraft:grass";
public const GRASS_BLOCK = "minecraft:grass_block";
public const GRASS_PATH = "minecraft:grass_path";
public const GRAVEL = "minecraft:gravel";
public const GRAY_CANDLE = "minecraft:gray_candle";
@ -553,11 +577,13 @@ final class BlockTypeNames{
public const HARD_YELLOW_STAINED_GLASS_PANE = "minecraft:hard_yellow_stained_glass_pane";
public const HARDENED_CLAY = "minecraft:hardened_clay";
public const HAY_BLOCK = "minecraft:hay_block";
public const HEAVY_CORE = "minecraft:heavy_core";
public const HEAVY_WEIGHTED_PRESSURE_PLATE = "minecraft:heavy_weighted_pressure_plate";
public const HONEY_BLOCK = "minecraft:honey_block";
public const HONEYCOMB_BLOCK = "minecraft:honeycomb_block";
public const HOPPER = "minecraft:hopper";
public const HORN_CORAL = "minecraft:horn_coral";
public const HORN_CORAL_FAN = "minecraft:horn_coral_fan";
public const ICE = "minecraft:ice";
public const INFESTED_DEEPSLATE = "minecraft:infested_deepslate";
public const INFO_UPDATE = "minecraft:info_update";
@ -572,16 +598,21 @@ final class BlockTypeNames{
public const JUKEBOX = "minecraft:jukebox";
public const JUNGLE_BUTTON = "minecraft:jungle_button";
public const JUNGLE_DOOR = "minecraft:jungle_door";
public const JUNGLE_DOUBLE_SLAB = "minecraft:jungle_double_slab";
public const JUNGLE_FENCE = "minecraft:jungle_fence";
public const JUNGLE_FENCE_GATE = "minecraft:jungle_fence_gate";
public const JUNGLE_HANGING_SIGN = "minecraft:jungle_hanging_sign";
public const JUNGLE_LEAVES = "minecraft:jungle_leaves";
public const JUNGLE_LOG = "minecraft:jungle_log";
public const JUNGLE_PLANKS = "minecraft:jungle_planks";
public const JUNGLE_PRESSURE_PLATE = "minecraft:jungle_pressure_plate";
public const JUNGLE_SAPLING = "minecraft:jungle_sapling";
public const JUNGLE_SLAB = "minecraft:jungle_slab";
public const JUNGLE_STAIRS = "minecraft:jungle_stairs";
public const JUNGLE_STANDING_SIGN = "minecraft:jungle_standing_sign";
public const JUNGLE_TRAPDOOR = "minecraft:jungle_trapdoor";
public const JUNGLE_WALL_SIGN = "minecraft:jungle_wall_sign";
public const JUNGLE_WOOD = "minecraft:jungle_wood";
public const KELP = "minecraft:kelp";
public const LADDER = "minecraft:ladder";
public const LANTERN = "minecraft:lantern";
@ -589,8 +620,6 @@ final class BlockTypeNames{
public const LAPIS_ORE = "minecraft:lapis_ore";
public const LARGE_AMETHYST_BUD = "minecraft:large_amethyst_bud";
public const LAVA = "minecraft:lava";
public const LEAVES = "minecraft:leaves";
public const LEAVES2 = "minecraft:leaves2";
public const LECTERN = "minecraft:lectern";
public const LEVER = "minecraft:lever";
public const LIGHT_BLOCK = "minecraft:light_block";
@ -617,6 +646,7 @@ final class BlockTypeNames{
public const LIGHT_GRAY_WOOL = "minecraft:light_gray_wool";
public const LIGHT_WEIGHTED_PRESSURE_PLATE = "minecraft:light_weighted_pressure_plate";
public const LIGHTNING_ROD = "minecraft:lightning_rod";
public const LILY_OF_THE_VALLEY = "minecraft:lily_of_the_valley";
public const LIME_CANDLE = "minecraft:lime_candle";
public const LIME_CANDLE_CAKE = "minecraft:lime_candle_cake";
public const LIME_CARPET = "minecraft:lime_carpet";
@ -698,11 +728,16 @@ final class BlockTypeNames{
public const NETHERREACTOR = "minecraft:netherreactor";
public const NORMAL_STONE_STAIRS = "minecraft:normal_stone_stairs";
public const NOTEBLOCK = "minecraft:noteblock";
public const OAK_DOUBLE_SLAB = "minecraft:oak_double_slab";
public const OAK_FENCE = "minecraft:oak_fence";
public const OAK_HANGING_SIGN = "minecraft:oak_hanging_sign";
public const OAK_LEAVES = "minecraft:oak_leaves";
public const OAK_LOG = "minecraft:oak_log";
public const OAK_PLANKS = "minecraft:oak_planks";
public const OAK_SAPLING = "minecraft:oak_sapling";
public const OAK_SLAB = "minecraft:oak_slab";
public const OAK_STAIRS = "minecraft:oak_stairs";
public const OAK_WOOD = "minecraft:oak_wood";
public const OBSERVER = "minecraft:observer";
public const OBSIDIAN = "minecraft:obsidian";
public const OCHRE_FROGLIGHT = "minecraft:ochre_froglight";
@ -716,7 +751,9 @@ final class BlockTypeNames{
public const ORANGE_STAINED_GLASS = "minecraft:orange_stained_glass";
public const ORANGE_STAINED_GLASS_PANE = "minecraft:orange_stained_glass_pane";
public const ORANGE_TERRACOTTA = "minecraft:orange_terracotta";
public const ORANGE_TULIP = "minecraft:orange_tulip";
public const ORANGE_WOOL = "minecraft:orange_wool";
public const OXEYE_DAISY = "minecraft:oxeye_daisy";
public const OXIDIZED_CHISELED_COPPER = "minecraft:oxidized_chiseled_copper";
public const OXIDIZED_COPPER = "minecraft:oxidized_copper";
public const OXIDIZED_COPPER_BULB = "minecraft:oxidized_copper_bulb";
@ -741,6 +778,7 @@ final class BlockTypeNames{
public const PINK_STAINED_GLASS = "minecraft:pink_stained_glass";
public const PINK_STAINED_GLASS_PANE = "minecraft:pink_stained_glass_pane";
public const PINK_TERRACOTTA = "minecraft:pink_terracotta";
public const PINK_TULIP = "minecraft:pink_tulip";
public const PINK_WOOL = "minecraft:pink_wool";
public const PISTON = "minecraft:piston";
public const PISTON_ARM_COLLISION = "minecraft:piston_arm_collision";
@ -777,6 +815,7 @@ final class BlockTypeNames{
public const POLISHED_TUFF_SLAB = "minecraft:polished_tuff_slab";
public const POLISHED_TUFF_STAIRS = "minecraft:polished_tuff_stairs";
public const POLISHED_TUFF_WALL = "minecraft:polished_tuff_wall";
public const POPPY = "minecraft:poppy";
public const PORTAL = "minecraft:portal";
public const POTATOES = "minecraft:potatoes";
public const POWDER_SNOW = "minecraft:powder_snow";
@ -813,7 +852,6 @@ final class BlockTypeNames{
public const RED_CARPET = "minecraft:red_carpet";
public const RED_CONCRETE = "minecraft:red_concrete";
public const RED_CONCRETE_POWDER = "minecraft:red_concrete_powder";
public const RED_FLOWER = "minecraft:red_flower";
public const RED_GLAZED_TERRACOTTA = "minecraft:red_glazed_terracotta";
public const RED_MUSHROOM = "minecraft:red_mushroom";
public const RED_MUSHROOM_BLOCK = "minecraft:red_mushroom_block";
@ -825,6 +863,7 @@ final class BlockTypeNames{
public const RED_STAINED_GLASS = "minecraft:red_stained_glass";
public const RED_STAINED_GLASS_PANE = "minecraft:red_stained_glass_pane";
public const RED_TERRACOTTA = "minecraft:red_terracotta";
public const RED_TULIP = "minecraft:red_tulip";
public const RED_WOOL = "minecraft:red_wool";
public const REDSTONE_BLOCK = "minecraft:redstone_block";
public const REDSTONE_LAMP = "minecraft:redstone_lamp";
@ -839,7 +878,6 @@ final class BlockTypeNames{
public const SAND = "minecraft:sand";
public const SANDSTONE = "minecraft:sandstone";
public const SANDSTONE_STAIRS = "minecraft:sandstone_stairs";
public const SAPLING = "minecraft:sapling";
public const SCAFFOLDING = "minecraft:scaffolding";
public const SCULK = "minecraft:sculk";
public const SCULK_CATALYST = "minecraft:sculk_catalyst";
@ -875,16 +913,21 @@ final class BlockTypeNames{
public const SPORE_BLOSSOM = "minecraft:spore_blossom";
public const SPRUCE_BUTTON = "minecraft:spruce_button";
public const SPRUCE_DOOR = "minecraft:spruce_door";
public const SPRUCE_DOUBLE_SLAB = "minecraft:spruce_double_slab";
public const SPRUCE_FENCE = "minecraft:spruce_fence";
public const SPRUCE_FENCE_GATE = "minecraft:spruce_fence_gate";
public const SPRUCE_HANGING_SIGN = "minecraft:spruce_hanging_sign";
public const SPRUCE_LEAVES = "minecraft:spruce_leaves";
public const SPRUCE_LOG = "minecraft:spruce_log";
public const SPRUCE_PLANKS = "minecraft:spruce_planks";
public const SPRUCE_PRESSURE_PLATE = "minecraft:spruce_pressure_plate";
public const SPRUCE_SAPLING = "minecraft:spruce_sapling";
public const SPRUCE_SLAB = "minecraft:spruce_slab";
public const SPRUCE_STAIRS = "minecraft:spruce_stairs";
public const SPRUCE_STANDING_SIGN = "minecraft:spruce_standing_sign";
public const SPRUCE_TRAPDOOR = "minecraft:spruce_trapdoor";
public const SPRUCE_WALL_SIGN = "minecraft:spruce_wall_sign";
public const SPRUCE_WOOD = "minecraft:spruce_wood";
public const STANDING_BANNER = "minecraft:standing_banner";
public const STANDING_SIGN = "minecraft:standing_sign";
public const STICKY_PISTON = "minecraft:sticky_piston";
@ -902,18 +945,24 @@ final class BlockTypeNames{
public const STONECUTTER = "minecraft:stonecutter";
public const STONECUTTER_BLOCK = "minecraft:stonecutter_block";
public const STRIPPED_ACACIA_LOG = "minecraft:stripped_acacia_log";
public const STRIPPED_ACACIA_WOOD = "minecraft:stripped_acacia_wood";
public const STRIPPED_BAMBOO_BLOCK = "minecraft:stripped_bamboo_block";
public const STRIPPED_BIRCH_LOG = "minecraft:stripped_birch_log";
public const STRIPPED_BIRCH_WOOD = "minecraft:stripped_birch_wood";
public const STRIPPED_CHERRY_LOG = "minecraft:stripped_cherry_log";
public const STRIPPED_CHERRY_WOOD = "minecraft:stripped_cherry_wood";
public const STRIPPED_CRIMSON_HYPHAE = "minecraft:stripped_crimson_hyphae";
public const STRIPPED_CRIMSON_STEM = "minecraft:stripped_crimson_stem";
public const STRIPPED_DARK_OAK_LOG = "minecraft:stripped_dark_oak_log";
public const STRIPPED_DARK_OAK_WOOD = "minecraft:stripped_dark_oak_wood";
public const STRIPPED_JUNGLE_LOG = "minecraft:stripped_jungle_log";
public const STRIPPED_JUNGLE_WOOD = "minecraft:stripped_jungle_wood";
public const STRIPPED_MANGROVE_LOG = "minecraft:stripped_mangrove_log";
public const STRIPPED_MANGROVE_WOOD = "minecraft:stripped_mangrove_wood";
public const STRIPPED_OAK_LOG = "minecraft:stripped_oak_log";
public const STRIPPED_OAK_WOOD = "minecraft:stripped_oak_wood";
public const STRIPPED_SPRUCE_LOG = "minecraft:stripped_spruce_log";
public const STRIPPED_SPRUCE_WOOD = "minecraft:stripped_spruce_wood";
public const STRIPPED_WARPED_HYPHAE = "minecraft:stripped_warped_hyphae";
public const STRIPPED_WARPED_STEM = "minecraft:stripped_warped_stem";
public const STRUCTURE_BLOCK = "minecraft:structure_block";
@ -934,6 +983,7 @@ final class BlockTypeNames{
public const TRIP_WIRE = "minecraft:trip_wire";
public const TRIPWIRE_HOOK = "minecraft:tripwire_hook";
public const TUBE_CORAL = "minecraft:tube_coral";
public const TUBE_CORAL_FAN = "minecraft:tube_coral_fan";
public const TUFF = "minecraft:tuff";
public const TUFF_BRICK_DOUBLE_SLAB = "minecraft:tuff_brick_double_slab";
public const TUFF_BRICK_SLAB = "minecraft:tuff_brick_slab";
@ -952,6 +1002,7 @@ final class BlockTypeNames{
public const UNLIT_REDSTONE_TORCH = "minecraft:unlit_redstone_torch";
public const UNPOWERED_COMPARATOR = "minecraft:unpowered_comparator";
public const UNPOWERED_REPEATER = "minecraft:unpowered_repeater";
public const VAULT = "minecraft:vault";
public const VERDANT_FROGLIGHT = "minecraft:verdant_froglight";
public const VINE = "minecraft:vine";
public const WALL_BANNER = "minecraft:wall_banner";
@ -1040,13 +1091,12 @@ final class BlockTypeNames{
public const WHITE_STAINED_GLASS = "minecraft:white_stained_glass";
public const WHITE_STAINED_GLASS_PANE = "minecraft:white_stained_glass_pane";
public const WHITE_TERRACOTTA = "minecraft:white_terracotta";
public const WHITE_TULIP = "minecraft:white_tulip";
public const WHITE_WOOL = "minecraft:white_wool";
public const WITHER_ROSE = "minecraft:wither_rose";
public const WOOD = "minecraft:wood";
public const WOODEN_BUTTON = "minecraft:wooden_button";
public const WOODEN_DOOR = "minecraft:wooden_door";
public const WOODEN_PRESSURE_PLATE = "minecraft:wooden_pressure_plate";
public const WOODEN_SLAB = "minecraft:wooden_slab";
public const YELLOW_CANDLE = "minecraft:yellow_candle";
public const YELLOW_CANDLE_CAKE = "minecraft:yellow_candle_cake";
public const YELLOW_CARPET = "minecraft:yellow_carpet";

View File

@ -203,8 +203,8 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->registerFlatCoralSerializers();
$this->registerCauldronSerializers();
$this->registerFlatWoodBlockSerializers();
$this->registerLegacyWoodBlockSerializers();
$this->registerLeavesSerializers();
$this->registerSaplingSerializers();
$this->registerSimpleSerializers();
$this->registerSerializers();
}
@ -538,6 +538,20 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL : Ids::TUBE_CORAL,
}
));
$this->map(Blocks::CORAL_FAN(), fn(FloorCoralFan $block) => Writer::create(
match($block->getCoralType()){
CoralType::BRAIN => $block->isDead() ? Ids::DEAD_BRAIN_CORAL_FAN : Ids::BRAIN_CORAL_FAN,
CoralType::BUBBLE => $block->isDead() ? Ids::DEAD_BUBBLE_CORAL_FAN : Ids::BUBBLE_CORAL_FAN,
CoralType::FIRE => $block->isDead() ? Ids::DEAD_FIRE_CORAL_FAN : Ids::FIRE_CORAL_FAN,
CoralType::HORN => $block->isDead() ? Ids::DEAD_HORN_CORAL_FAN : Ids::HORN_CORAL_FAN,
CoralType::TUBE => $block->isDead() ? Ids::DEAD_TUBE_CORAL_FAN : Ids::TUBE_CORAL_FAN,
})
->writeInt(StateNames::CORAL_FAN_DIRECTION, match($axis = $block->getAxis()){
Axis::X => 0,
Axis::Z => 1,
default => throw new BlockStateSerializeException("Invalid axis {$axis}"),
}));
}
private function registerCauldronSerializers() : void{
@ -558,9 +572,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::ACACIA_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::ACACIA_TRAPDOOR)));
$this->map(Blocks::ACACIA_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::ACACIA_WALL_SIGN)));
$this->mapLog(Blocks::ACACIA_LOG(), Ids::ACACIA_LOG, Ids::STRIPPED_ACACIA_LOG);
$this->mapLog(Blocks::ACACIA_WOOD(), Ids::ACACIA_WOOD, Ids::STRIPPED_ACACIA_WOOD);
$this->mapSimple(Blocks::ACACIA_FENCE(), Ids::ACACIA_FENCE);
$this->mapSimple(Blocks::ACACIA_PLANKS(), Ids::ACACIA_PLANKS);
//wood and slabs still use the old way of storing wood type
$this->mapSlab(Blocks::ACACIA_SLAB(), Ids::ACACIA_SLAB, Ids::ACACIA_DOUBLE_SLAB);
$this->map(Blocks::BIRCH_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::BIRCH_BUTTON)));
$this->map(Blocks::BIRCH_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::BIRCH_DOOR)));
@ -570,10 +585,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::BIRCH_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::BIRCH_TRAPDOOR)));
$this->map(Blocks::BIRCH_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::BIRCH_WALL_SIGN)));
$this->mapLog(Blocks::BIRCH_LOG(), Ids::BIRCH_LOG, Ids::STRIPPED_BIRCH_LOG);
$this->mapLog(Blocks::BIRCH_WOOD(), Ids::BIRCH_WOOD, Ids::STRIPPED_BIRCH_WOOD);
$this->mapSimple(Blocks::BIRCH_FENCE(), Ids::BIRCH_FENCE);
$this->mapSimple(Blocks::BIRCH_PLANKS(), Ids::BIRCH_PLANKS);
$this->mapSlab(Blocks::BIRCH_SLAB(), Ids::BIRCH_SLAB, Ids::BIRCH_DOUBLE_SLAB);
$this->mapStairs(Blocks::BIRCH_STAIRS(), Ids::BIRCH_STAIRS);
//wood and slabs still use the old way of storing wood type
$this->map(Blocks::CHERRY_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::CHERRY_BUTTON)));
$this->map(Blocks::CHERRY_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::CHERRY_DOOR)));
@ -621,10 +637,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::DARK_OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::DARK_OAK_TRAPDOOR)));
$this->map(Blocks::DARK_OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::DARKOAK_WALL_SIGN)));
$this->mapLog(Blocks::DARK_OAK_LOG(), Ids::DARK_OAK_LOG, Ids::STRIPPED_DARK_OAK_LOG);
$this->mapLog(Blocks::DARK_OAK_WOOD(), Ids::DARK_OAK_WOOD, Ids::STRIPPED_DARK_OAK_WOOD);
$this->mapSimple(Blocks::DARK_OAK_FENCE(), Ids::DARK_OAK_FENCE);
$this->mapSimple(Blocks::DARK_OAK_PLANKS(), Ids::DARK_OAK_PLANKS);
$this->mapSlab(Blocks::DARK_OAK_SLAB(), Ids::DARK_OAK_SLAB, Ids::DARK_OAK_DOUBLE_SLAB);
$this->mapStairs(Blocks::DARK_OAK_STAIRS(), Ids::DARK_OAK_STAIRS);
//wood and slabs still use the old way of storing wood type
$this->map(Blocks::JUNGLE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::JUNGLE_BUTTON)));
$this->map(Blocks::JUNGLE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::JUNGLE_DOOR)));
@ -634,10 +651,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::JUNGLE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::JUNGLE_TRAPDOOR)));
$this->map(Blocks::JUNGLE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::JUNGLE_WALL_SIGN)));
$this->mapLog(Blocks::JUNGLE_LOG(), Ids::JUNGLE_LOG, Ids::STRIPPED_JUNGLE_LOG);
$this->mapLog(Blocks::JUNGLE_WOOD(), Ids::JUNGLE_WOOD, Ids::STRIPPED_JUNGLE_WOOD);
$this->mapSimple(Blocks::JUNGLE_FENCE(), Ids::JUNGLE_FENCE);
$this->mapSimple(Blocks::JUNGLE_PLANKS(), Ids::JUNGLE_PLANKS);
$this->mapSlab(Blocks::JUNGLE_SLAB(), Ids::JUNGLE_SLAB, Ids::JUNGLE_DOUBLE_SLAB);
$this->mapStairs(Blocks::JUNGLE_STAIRS(), Ids::JUNGLE_STAIRS);
//wood and slabs still use the old way of storing wood type
$this->map(Blocks::MANGROVE_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::MANGROVE_BUTTON)));
$this->map(Blocks::MANGROVE_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::MANGROVE_DOOR)));
@ -671,10 +689,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::TRAPDOOR)));
$this->map(Blocks::OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WALL_SIGN)));
$this->mapLog(Blocks::OAK_LOG(), Ids::OAK_LOG, Ids::STRIPPED_OAK_LOG);
$this->mapLog(Blocks::OAK_WOOD(), Ids::OAK_WOOD, Ids::STRIPPED_OAK_WOOD);
$this->mapSimple(Blocks::OAK_FENCE(), Ids::OAK_FENCE);
$this->mapSimple(Blocks::OAK_PLANKS(), Ids::OAK_PLANKS);
$this->mapSlab(Blocks::OAK_SLAB(), Ids::OAK_SLAB, Ids::OAK_DOUBLE_SLAB);
$this->mapStairs(Blocks::OAK_STAIRS(), Ids::OAK_STAIRS);
//wood and slabs still use the old way of storing wood type
$this->map(Blocks::SPRUCE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::SPRUCE_BUTTON)));
$this->map(Blocks::SPRUCE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::SPRUCE_DOOR)));
@ -684,8 +703,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::SPRUCE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::SPRUCE_TRAPDOOR)));
$this->map(Blocks::SPRUCE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::SPRUCE_WALL_SIGN)));
$this->mapLog(Blocks::SPRUCE_LOG(), Ids::SPRUCE_LOG, Ids::STRIPPED_SPRUCE_LOG);
$this->mapLog(Blocks::SPRUCE_WOOD(), Ids::SPRUCE_WOOD, Ids::STRIPPED_SPRUCE_WOOD);
$this->mapSimple(Blocks::SPRUCE_FENCE(), Ids::SPRUCE_FENCE);
$this->mapSimple(Blocks::SPRUCE_PLANKS(), Ids::SPRUCE_PLANKS);
$this->mapSlab(Blocks::SPRUCE_SLAB(), Ids::SPRUCE_SLAB, Ids::SPRUCE_DOUBLE_SLAB);
$this->mapStairs(Blocks::SPRUCE_STAIRS(), Ids::SPRUCE_STAIRS);
//wood and slabs still use the old way of storing wood type
@ -704,30 +725,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->mapStairs(Blocks::WARPED_STAIRS(), Ids::WARPED_STAIRS);
}
private function registerLegacyWoodBlockSerializers() : void{
foreach([
StringValues::WOOD_TYPE_ACACIA => Blocks::ACACIA_SLAB(),
StringValues::WOOD_TYPE_BIRCH => Blocks::BIRCH_SLAB(),
StringValues::WOOD_TYPE_DARK_OAK => Blocks::DARK_OAK_SLAB(),
StringValues::WOOD_TYPE_JUNGLE => Blocks::JUNGLE_SLAB(),
StringValues::WOOD_TYPE_OAK => Blocks::OAK_SLAB(),
StringValues::WOOD_TYPE_SPRUCE => Blocks::SPRUCE_SLAB(),
] as $woodType => $block){
$this->map($block, fn(Slab $block) => Helper::encodeWoodenSlab($block, $woodType));
}
foreach([
Blocks::ACACIA_WOOD(),
Blocks::BIRCH_WOOD(),
Blocks::DARK_OAK_WOOD(),
Blocks::JUNGLE_WOOD(),
Blocks::OAK_WOOD(),
Blocks::SPRUCE_WOOD(),
] as $block){
$this->map($block, fn(Wood $block) => Helper::encodeAllSidedLog($block));
}
}
private function registerLeavesSerializers() : void{
//flattened IDs
$this->map(Blocks::AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES)));
@ -736,12 +733,25 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::MANGROVE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::MANGROVE_LEAVES)));
//legacy mess
$this->map(Blocks::ACACIA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves2($block, StringValues::NEW_LEAF_TYPE_ACACIA));
$this->map(Blocks::BIRCH_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_BIRCH));
$this->map(Blocks::DARK_OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves2($block, StringValues::NEW_LEAF_TYPE_DARK_OAK));
$this->map(Blocks::JUNGLE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_JUNGLE));
$this->map(Blocks::OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_OAK));
$this->map(Blocks::SPRUCE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_SPRUCE));
$this->map(Blocks::ACACIA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::ACACIA_LEAVES)));
$this->map(Blocks::BIRCH_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::BIRCH_LEAVES)));
$this->map(Blocks::DARK_OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::DARK_OAK_LEAVES)));
$this->map(Blocks::JUNGLE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::JUNGLE_LEAVES)));
$this->map(Blocks::OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::OAK_LEAVES)));
$this->map(Blocks::SPRUCE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::SPRUCE_LEAVES)));
}
private function registerSaplingSerializers() : void{
foreach([
Ids::ACACIA_SAPLING => Blocks::ACACIA_SAPLING(),
Ids::BIRCH_SAPLING => Blocks::BIRCH_SAPLING(),
Ids::DARK_OAK_SAPLING => Blocks::DARK_OAK_SAPLING(),
Ids::JUNGLE_SAPLING => Blocks::JUNGLE_SAPLING(),
Ids::OAK_SAPLING => Blocks::OAK_SAPLING(),
Ids::SPRUCE_SAPLING => Blocks::SPRUCE_SAPLING(),
] as $id => $block){
$this->map($block, fn(Sapling $block) => Helper::encodeSapling($block, new Writer($id)));
}
}
private function registerSimpleSerializers() : void{
@ -927,7 +937,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->mapSimple(Blocks::GOLD(), Ids::GOLD_BLOCK);
$this->mapSimple(Blocks::GOLD_ORE(), Ids::GOLD_ORE);
$this->mapSimple(Blocks::GRANITE(), Ids::GRANITE);
$this->mapSimple(Blocks::GRASS(), Ids::GRASS);
$this->mapSimple(Blocks::GRASS(), Ids::GRASS_BLOCK);
$this->mapSimple(Blocks::GRASS_PATH(), Ids::GRASS_PATH);
$this->mapSimple(Blocks::GRAVEL(), Ids::GRAVEL);
$this->mapSimple(Blocks::HANGING_ROOTS(), Ids::HANGING_ROOTS);
@ -1002,16 +1012,26 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->mapSimple(Blocks::WARPED_WART_BLOCK(), Ids::WARPED_WART_BLOCK);
$this->mapSimple(Blocks::WARPED_ROOTS(), Ids::WARPED_ROOTS);
$this->mapSimple(Blocks::WITHER_ROSE(), Ids::WITHER_ROSE);
$this->mapSimple(Blocks::ALLIUM(), Ids::ALLIUM);
$this->mapSimple(Blocks::CORNFLOWER(), Ids::CORNFLOWER);
$this->mapSimple(Blocks::AZURE_BLUET(), Ids::AZURE_BLUET);
$this->mapSimple(Blocks::LILY_OF_THE_VALLEY(), Ids::LILY_OF_THE_VALLEY);
$this->mapSimple(Blocks::BLUE_ORCHID(), Ids::BLUE_ORCHID);
$this->mapSimple(Blocks::OXEYE_DAISY(), Ids::OXEYE_DAISY);
$this->mapSimple(Blocks::POPPY(), Ids::POPPY);
$this->mapSimple(Blocks::ORANGE_TULIP(), Ids::ORANGE_TULIP);
$this->mapSimple(Blocks::PINK_TULIP(), Ids::PINK_TULIP);
$this->mapSimple(Blocks::RED_TULIP(), Ids::RED_TULIP);
$this->mapSimple(Blocks::WHITE_TULIP(), Ids::WHITE_TULIP);
}
private function registerSerializers() : void{
$this->map(Blocks::ACACIA_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_ACACIA));
$this->map(Blocks::ACTIVATOR_RAIL(), function(ActivatorRail $block) : Writer{
return Writer::create(Ids::ACTIVATOR_RAIL)
->writeBool(StateNames::RAIL_DATA_BIT, $block->isPowered())
->writeInt(StateNames::RAIL_DIRECTION, $block->getShape());
});
$this->map(Blocks::ALLIUM(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_ALLIUM));
$this->map(Blocks::ALL_SIDED_MUSHROOM_STEM(), fn() => Writer::create(Ids::BROWN_MUSHROOM_BLOCK)
->writeInt(StateNames::HUGE_MUSHROOM_BITS, BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_STEM));
$this->map(Blocks::AMETHYST_CLUSTER(), fn(AmethystCluster $block) => Writer::create(
@ -1037,7 +1057,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
default => throw new BlockStateSerializeException("Invalid Anvil damage {$damage}"),
});
});
$this->map(Blocks::AZURE_BLUET(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_HOUSTONIA));
$this->map(Blocks::BAMBOO(), function(Bamboo $block) : Writer{
return Writer::create(Ids::BAMBOO)
->writeBool(StateNames::AGE_BIT, $block->isReady())
@ -1051,10 +1070,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
});
$this->map(Blocks::BAMBOO_SAPLING(), function(BambooSapling $block) : Writer{
return Writer::create(Ids::BAMBOO_SAPLING)
->writeBool(StateNames::AGE_BIT, $block->isReady())
//TODO: bug in MCPE
->writeString(StateNames::SAPLING_TYPE, StringValues::SAPLING_TYPE_OAK);
->writeBool(StateNames::AGE_BIT, $block->isReady());
});
$this->map(Blocks::BANNER(), function(FloorBanner $block) : Writer{
return Writer::create(Ids::STANDING_BANNER)
@ -1104,12 +1120,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
->writeString(StateNames::BIG_DRIPLEAF_TILT, StringValues::BIG_DRIPLEAF_TILT_NONE)
->writeBool(StateNames::BIG_DRIPLEAF_HEAD, false);
});
$this->map(Blocks::BIRCH_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_BIRCH));
$this->mapSlab(Blocks::BLACKSTONE_SLAB(), Ids::BLACKSTONE_SLAB, Ids::BLACKSTONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::BLACKSTONE_STAIRS(), Ids::BLACKSTONE_STAIRS);
$this->map(Blocks::BLACKSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::BLACKSTONE_WALL)));
$this->map(Blocks::BLAST_FURNACE(), fn(Furnace $block) => Helper::encodeFurnace($block, Ids::BLAST_FURNACE, Ids::LIT_BLAST_FURNACE));
$this->map(Blocks::BLUE_ORCHID(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_ORCHID));
$this->map(Blocks::BLUE_TORCH(), fn(Torch $block) => Helper::encodeColoredTorch($block, false, Writer::create(Ids::COLORED_TORCH_BP)));
$this->map(Blocks::BONE_BLOCK(), function(BoneBlock $block) : Writer{
return Writer::create(Ids::BONE_BLOCK)
@ -1266,22 +1280,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
->writeBool(StateNames::DEAD_BIT, $block->isDead())
->writeCoralType($block->getCoralType());
});
$this->map(Blocks::CORAL_FAN(), function(FloorCoralFan $block) : Writer{
return Writer::create($block->isDead() ? Ids::CORAL_FAN_DEAD : Ids::CORAL_FAN)
->writeCoralType($block->getCoralType())
->writeInt(StateNames::CORAL_FAN_DIRECTION, match($axis = $block->getAxis()){
Axis::X => 0,
Axis::Z => 1,
default => throw new BlockStateSerializeException("Invalid axis {$axis}"),
});
});
$this->map(Blocks::CORNFLOWER(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_CORNFLOWER));
$this->map(Blocks::CRACKED_STONE_BRICKS(), fn() => Helper::encodeStoneBricks(StringValues::STONE_BRICK_TYPE_CRACKED));
$this->map(Blocks::CUT_RED_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::RED_SANDSTONE, StringValues::SAND_STONE_TYPE_CUT));
$this->map(Blocks::CUT_RED_SANDSTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab4($block, StringValues::STONE_SLAB_TYPE_4_CUT_RED_SANDSTONE));
$this->map(Blocks::CUT_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::SANDSTONE, StringValues::SAND_STONE_TYPE_CUT));
$this->map(Blocks::CUT_SANDSTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab4($block, StringValues::STONE_SLAB_TYPE_4_CUT_SANDSTONE));
$this->map(Blocks::DARK_OAK_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_DARK_OAK));
$this->map(Blocks::DARK_PRISMARINE(), fn() => Writer::create(Ids::PRISMARINE)
->writeString(StateNames::PRISMARINE_BLOCK_TYPE, StringValues::PRISMARINE_BLOCK_TYPE_DARK));
$this->map(Blocks::DARK_PRISMARINE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab2($block, StringValues::STONE_SLAB_TYPE_2_PRISMARINE_DARK));
@ -1401,7 +1404,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::IRON_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::IRON_DOOR)));
$this->map(Blocks::IRON_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::IRON_TRAPDOOR)));
$this->map(Blocks::ITEM_FRAME(), fn(ItemFrame $block) => Helper::encodeItemFrame($block, Ids::FRAME));
$this->map(Blocks::JUNGLE_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_JUNGLE));
$this->map(Blocks::LAB_TABLE(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, StringValues::CHEMISTRY_TABLE_TYPE_LAB_TABLE, new Writer(Ids::CHEMISTRY_TABLE)));
$this->map(Blocks::LADDER(), function(Ladder $block) : Writer{
return Writer::create(Ids::LADDER)
@ -1441,7 +1443,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
->writeFacingDirection($block->getFacing());
});
$this->map(Blocks::LILAC(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_SYRINGA, Writer::create(Ids::DOUBLE_PLANT)));
$this->map(Blocks::LILY_OF_THE_VALLEY(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_LILY_OF_THE_VALLEY));
$this->map(Blocks::LIT_PUMPKIN(), function(LitPumpkin $block) : Writer{
return Writer::create(Ids::LIT_PUMPKIN)
->writeCardinalHorizontalFacing($block->getFacing());
@ -1485,16 +1486,12 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
return Writer::create(Ids::NETHER_WART)
->writeInt(StateNames::AGE, $block->getAge());
});
$this->map(Blocks::OAK_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_OAK));
$this->map(Blocks::ORANGE_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_ORANGE));
$this->map(Blocks::OXEYE_DAISY(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_OXEYE));
$this->map(Blocks::PEONY(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_PAEONIA, Writer::create(Ids::DOUBLE_PLANT)));
$this->map(Blocks::PINK_PETALS(), function(PinkPetals $block) : Writer{
return Writer::create(Ids::PINK_PETALS)
->writeCardinalHorizontalFacing($block->getFacing())
->writeInt(StateNames::GROWTH, $block->getCount() - 1);
});
$this->map(Blocks::PINK_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_PINK));
$this->map(Blocks::PITCHER_PLANT(), function(DoublePlant $block) : Writer{
return Writer::create(Ids::PITCHER_PLANT)
->writeBool(StateNames::UPPER_BLOCK_BIT, $block->isTop());
@ -1530,7 +1527,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->mapStairs(Blocks::POLISHED_DIORITE_STAIRS(), Ids::POLISHED_DIORITE_STAIRS);
$this->map(Blocks::POLISHED_GRANITE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_POLISHED_GRANITE));
$this->mapStairs(Blocks::POLISHED_GRANITE_STAIRS(), Ids::POLISHED_GRANITE_STAIRS);
$this->map(Blocks::POPPY(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_POPPY));
$this->map(Blocks::POTATOES(), fn(Potato $block) => Helper::encodeCrops($block, new Writer(Ids::POTATOES)));
$this->map(Blocks::POWERED_RAIL(), function(PoweredRail $block) : Writer{
return Writer::create(Ids::GOLDEN_RAIL)
@ -1604,7 +1600,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->mapStairs(Blocks::RED_SANDSTONE_STAIRS(), Ids::RED_SANDSTONE_STAIRS);
$this->map(Blocks::RED_SANDSTONE_WALL(), fn(Wall $block) => Helper::encodeLegacyWall($block, StringValues::WALL_BLOCK_TYPE_RED_SANDSTONE));
$this->map(Blocks::RED_TORCH(), fn(Torch $block) => Helper::encodeColoredTorch($block, false, Writer::create(Ids::COLORED_TORCH_RG)));
$this->map(Blocks::RED_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_RED));
$this->map(Blocks::ROSE_BUSH(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_ROSE, Writer::create(Ids::DOUBLE_PLANT)));
$this->map(Blocks::SAND(), fn() => Writer::create(Ids::SAND)
->writeString(StateNames::SAND_TYPE, StringValues::SAND_TYPE_NORMAL));
@ -1654,7 +1649,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
return Writer::create(Ids::SPONGE)
->writeString(StateNames::SPONGE_TYPE, $block->isWet() ? StringValues::SPONGE_TYPE_WET : StringValues::SPONGE_TYPE_DRY);
});
$this->map(Blocks::SPRUCE_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_SPRUCE));
$this->map(Blocks::STONECUTTER(), fn(Stonecutter $block) => Writer::create(Ids::STONECUTTER_BLOCK)
->writeCardinalHorizontalFacing($block->getFacing()));
$this->map(Blocks::STONE_BRICKS(), fn() => Helper::encodeStoneBricks(StringValues::STONE_BRICK_TYPE_DEFAULT));
@ -1747,6 +1741,5 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
->writeInt(StateNames::REDSTONE_SIGNAL, $block->getOutputSignalStrength());
});
$this->map(Blocks::WHEAT(), fn(Wheat $block) => Helper::encodeCrops($block, new Writer(Ids::WHEAT)));
$this->map(Blocks::WHITE_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_WHITE));
}
}

View File

@ -41,6 +41,7 @@ use pocketmine\block\Liquid;
use pocketmine\block\RedMushroomBlock;
use pocketmine\block\RedstoneComparator;
use pocketmine\block\RedstoneRepeater;
use pocketmine\block\Sapling;
use pocketmine\block\SimplePressurePlate;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
@ -149,7 +150,6 @@ final class BlockStateDeserializerHelper{
/** @throws BlockStateDeserializeException */
public static function decodeFloorCoralFan(FloorCoralFan $block, BlockStateReader $in) : FloorCoralFan{
return $block
->setCoralType($in->readCoralType())
->setAxis(match($in->readBoundedInt(BlockStateNames::CORAL_FAN_DIRECTION, 0, 1)){
0 => Axis::X,
1 => Axis::Z,
@ -220,6 +220,12 @@ final class BlockStateDeserializerHelper{
->setDelay($in->readBoundedInt(BlockStateNames::REPEATER_DELAY, 0, 3) + 1);
}
/** @throws BlockStateDeserializeException */
public static function decodeSapling(Sapling $block, BlockStateReader $in) : Sapling{
return $block
->setReady($in->readBool(BlockStateNames::AGE_BIT));
}
/** @throws BlockStateDeserializeException */
public static function decodeSimplePressurePlate(SimplePressurePlate $block, BlockStateReader $in) : SimplePressurePlate{
//TODO: not sure what the deal is here ... seems like a mojang bug / artifact of bad implementation?
@ -362,18 +368,4 @@ final class BlockStateDeserializerHelper{
default => throw $in->badValueException(BlockStateNames::STONE_SLAB_TYPE_4, $type),
};
}
/** @throws BlockStateDeserializeException */
public static function mapWoodenSlabType(BlockStateReader $in) : Slab{
// * wood_type (StringTag) = acacia, birch, dark_oak, jungle, oak, spruce
return match($type = $in->readString(BlockStateNames::WOOD_TYPE)){
StringValues::WOOD_TYPE_ACACIA => VanillaBlocks::ACACIA_SLAB(),
StringValues::WOOD_TYPE_BIRCH => VanillaBlocks::BIRCH_SLAB(),
StringValues::WOOD_TYPE_DARK_OAK => VanillaBlocks::DARK_OAK_SLAB(),
StringValues::WOOD_TYPE_JUNGLE => VanillaBlocks::JUNGLE_SLAB(),
StringValues::WOOD_TYPE_OAK => VanillaBlocks::OAK_SLAB(),
StringValues::WOOD_TYPE_SPRUCE => VanillaBlocks::SPRUCE_SLAB(),
default => throw $in->badValueException(BlockStateNames::WOOD_TYPE, $type),
};
}
}

View File

@ -38,20 +38,24 @@ use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\tag\Tag;
use pocketmine\utils\Utils;
use function array_keys;
use function count;
use function get_class;
use function implode;
final class BlockStateReader{
/**
* @var true[]
* @phpstan-var array<string, true>
* @var Tag[]
* @phpstan-var array<string, Tag>
*/
private array $usedStates = [];
private array $unusedStates;
public function __construct(
private BlockStateData $data
){}
){
$this->unusedStates = $this->data->getStates();
}
public function missingOrWrongTypeException(string $name, ?Tag $tag) : BlockStateDeserializeException{
return new BlockStateDeserializeException("Property \"$name\" " . ($tag !== null ? "has unexpected type " . get_class($tag) : "is missing"));
@ -66,7 +70,7 @@ final class BlockStateReader{
/** @throws BlockStateDeserializeException */
public function readBool(string $name) : bool{
$this->usedStates[$name] = true;
unset($this->unusedStates[$name]);
$tag = $this->data->getState($name);
if($tag instanceof ByteTag){
switch($tag->getValue()){
@ -80,7 +84,7 @@ final class BlockStateReader{
/** @throws BlockStateDeserializeException */
public function readInt(string $name) : int{
$this->usedStates[$name] = true;
unset($this->unusedStates[$name]);
$tag = $this->data->getState($name);
if($tag instanceof IntTag){
return $tag->getValue();
@ -99,7 +103,7 @@ final class BlockStateReader{
/** @throws BlockStateDeserializeException */
public function readString(string $name) : string{
$this->usedStates[$name] = true;
unset($this->unusedStates[$name]);
//TODO: only allow a specific set of values (strings are primarily used for enums)
$tag = $this->data->getState($name);
if($tag instanceof StringTag){
@ -346,7 +350,7 @@ final class BlockStateReader{
*/
public function ignored(string $name) : void{
if($this->data->getState($name) !== null){
$this->usedStates[$name] = true;
unset($this->unusedStates[$name]);
}else{
throw $this->missingOrWrongTypeException($name, null);
}
@ -363,10 +367,8 @@ final class BlockStateReader{
* @throws BlockStateDeserializeException
*/
public function checkUnreadProperties() : void{
foreach(Utils::stringifyKeys($this->data->getStates()) as $name => $tag){
if(!isset($this->usedStates[$name])){
throw new BlockStateDeserializeException("Unread property \"$name\"");
}
if(count($this->unusedStates) > 0){
throw new BlockStateDeserializeException("Unread properties: " . implode(", ", array_keys($this->unusedStates)));
}
}
}

View File

@ -56,14 +56,6 @@ use pocketmine\data\bedrock\MushroomBlockTypeIdMap;
use pocketmine\math\Facing;
final class BlockStateSerializerHelper{
public static function encodeAllSidedLog(Wood $block) : Writer{
return Writer::create(Ids::WOOD)
->writeBool(BlockStateNames::STRIPPED_BIT, $block->isStripped())
->writePillarAxis($block->getAxis())
->writeLegacyWoodType($block->getWoodType());
}
public static function encodeButton(Button $block, Writer $out) : Writer{
return $out
->writeFacingDirection($block->getFacing())
@ -151,16 +143,6 @@ final class BlockStateSerializerHelper{
->writeBool(BlockStateNames::UPDATE_BIT, $block->isCheckDecay());
}
public static function encodeLeaves1(Leaves $block, string $type) : Writer{
return self::encodeLeaves($block, Writer::create(Ids::LEAVES)
->writeString(BlockStateNames::OLD_LEAF_TYPE, $type));
}
public static function encodeLeaves2(Leaves $block, string $type) : Writer{
return self::encodeLeaves($block, Writer::create(Ids::LEAVES2)
->writeString(BlockStateNames::NEW_LEAF_TYPE, $type));
}
public static function encodeLiquid(Liquid $block, string $stillId, string $flowingId) : Writer{
return Writer::create($block->isStill() ? $stillId : $flowingId)
->writeInt(BlockStateNames::LIQUID_DEPTH, $block->getDecay() | ($block->isFalling() ? 0x8 : 0));
@ -185,18 +167,13 @@ final class BlockStateSerializerHelper{
->writePillarAxis($axis); //this isn't needed for all types, but we have to write it anyway
}
public static function encodeRedFlower(string $type) : Writer{
return Writer::create(Ids::RED_FLOWER)->writeString(BlockStateNames::FLOWER_TYPE, $type);
}
public static function encodeSandstone(string $id, string $type) : Writer{
return Writer::create($id)->writeString(BlockStateNames::SAND_STONE_TYPE, $type);
}
public static function encodeSapling(Sapling $block, string $type) : Writer{
return Writer::create(Ids::SAPLING)
->writeBool(BlockStateNames::AGE_BIT, $block->isReady())
->writeString(BlockStateNames::SAPLING_TYPE, $type);
public static function encodeSapling(Sapling $block, Writer $out) : Writer{
return $out
->writeBool(BlockStateNames::AGE_BIT, $block->isReady());
}
public static function encodeSimplePressurePlate(SimplePressurePlate $block, Writer $out) : Writer{
@ -279,9 +256,4 @@ final class BlockStateSerializerHelper{
return $out
->writeHorizontalFacing($block->getFacing());
}
public static function encodeWoodenSlab(Slab $block, string $typeValue) : Writer{
return self::encodeSlab($block, Ids::WOODEN_SLAB, Ids::DOUBLE_WOODEN_SLAB)
->writeString(BlockStateNames::WOOD_TYPE, $typeValue);
}
}

View File

@ -82,8 +82,8 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->registerFlatCoralDeserializers();
$this->registerCauldronDeserializers();
$this->registerFlatWoodBlockDeserializers();
$this->registerLegacyWoodBlockDeserializers();
$this->registerLeavesDeserializers();
$this->registerSaplingDeserializers();
$this->registerSimpleDeserializers();
$this->registerDeserializers();
}
@ -439,6 +439,17 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
] as $id => $coralType){
$this->mapSimple($id, fn() => Blocks::CORAL()->setCoralType($coralType)->setDead(true));
}
foreach([
[CoralType::BRAIN, Ids::BRAIN_CORAL_FAN, Ids::DEAD_BRAIN_CORAL_FAN],
[CoralType::BUBBLE, Ids::BUBBLE_CORAL_FAN, Ids::DEAD_BUBBLE_CORAL_FAN],
[CoralType::FIRE, Ids::FIRE_CORAL_FAN, Ids::DEAD_FIRE_CORAL_FAN],
[CoralType::HORN, Ids::HORN_CORAL_FAN, Ids::DEAD_HORN_CORAL_FAN],
[CoralType::TUBE, Ids::TUBE_CORAL_FAN, Ids::DEAD_TUBE_CORAL_FAN],
] as [$coralType, $aliveId, $deadId]){
$this->map($aliveId, fn(Reader $in) => Helper::decodeFloorCoralFan(Blocks::CORAL_FAN()->setCoralType($coralType)->setDead(false), $in));
$this->map($deadId, fn(Reader $in) => Helper::decodeFloorCoralFan(Blocks::CORAL_FAN()->setCoralType($coralType)->setDead(true), $in));
}
}
private function registerCauldronDeserializers() : void{
@ -468,10 +479,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->map(Ids::ACACIA_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::ACACIA_TRAPDOOR(), $in));
$this->map(Ids::ACACIA_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::ACACIA_WALL_SIGN(), $in));
$this->mapLog(Ids::ACACIA_LOG, Ids::STRIPPED_ACACIA_LOG, fn() => Blocks::ACACIA_LOG());
$this->mapLog(Ids::ACACIA_WOOD, Ids::STRIPPED_ACACIA_WOOD, fn() => Blocks::ACACIA_WOOD());
$this->mapSimple(Ids::ACACIA_FENCE, fn() => Blocks::ACACIA_FENCE());
$this->mapSimple(Ids::ACACIA_PLANKS, fn() => Blocks::ACACIA_PLANKS());
$this->mapSlab(Ids::ACACIA_SLAB, Ids::ACACIA_DOUBLE_SLAB, fn() => Blocks::ACACIA_SLAB());
$this->mapStairs(Ids::ACACIA_STAIRS, fn() => Blocks::ACACIA_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::BIRCH_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::BIRCH_BUTTON(), $in));
$this->map(Ids::BIRCH_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::BIRCH_DOOR(), $in));
@ -481,10 +493,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->map(Ids::BIRCH_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::BIRCH_TRAPDOOR(), $in));
$this->map(Ids::BIRCH_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::BIRCH_WALL_SIGN(), $in));
$this->mapLog(Ids::BIRCH_LOG, Ids::STRIPPED_BIRCH_LOG, fn() => Blocks::BIRCH_LOG());
$this->mapLog(Ids::BIRCH_WOOD, Ids::STRIPPED_BIRCH_WOOD, fn() => Blocks::BIRCH_WOOD());
$this->mapSimple(Ids::BIRCH_FENCE, fn() => Blocks::BIRCH_FENCE());
$this->mapSimple(Ids::BIRCH_PLANKS, fn() => Blocks::BIRCH_PLANKS());
$this->mapSlab(Ids::BIRCH_SLAB, Ids::BIRCH_DOUBLE_SLAB, fn() => Blocks::BIRCH_SLAB());
$this->mapStairs(Ids::BIRCH_STAIRS, fn() => Blocks::BIRCH_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::CHERRY_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::CHERRY_BUTTON(), $in));
$this->map(Ids::CHERRY_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::CHERRY_DOOR(), $in));
@ -526,10 +539,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->map(Ids::DARK_OAK_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::DARK_OAK_PRESSURE_PLATE(), $in));
$this->map(Ids::DARK_OAK_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::DARK_OAK_TRAPDOOR(), $in));
$this->mapLog(Ids::DARK_OAK_LOG, Ids::STRIPPED_DARK_OAK_LOG, fn() => Blocks::DARK_OAK_LOG());
$this->mapLog(Ids::DARK_OAK_WOOD, Ids::STRIPPED_DARK_OAK_WOOD, fn() => Blocks::DARK_OAK_WOOD());
$this->mapSimple(Ids::DARK_OAK_FENCE, fn() => Blocks::DARK_OAK_FENCE());
$this->mapSimple(Ids::DARK_OAK_PLANKS, fn() => Blocks::DARK_OAK_PLANKS());
$this->mapSlab(Ids::DARK_OAK_SLAB, Ids::DARK_OAK_DOUBLE_SLAB, fn() => Blocks::DARK_OAK_SLAB());
$this->mapStairs(Ids::DARK_OAK_STAIRS, fn() => Blocks::DARK_OAK_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::JUNGLE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::JUNGLE_BUTTON(), $in));
$this->map(Ids::JUNGLE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::JUNGLE_DOOR(), $in));
@ -539,10 +553,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->map(Ids::JUNGLE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::JUNGLE_TRAPDOOR(), $in));
$this->map(Ids::JUNGLE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::JUNGLE_WALL_SIGN(), $in));
$this->mapLog(Ids::JUNGLE_LOG, Ids::STRIPPED_JUNGLE_LOG, fn() => Blocks::JUNGLE_LOG());
$this->mapLog(Ids::JUNGLE_WOOD, Ids::STRIPPED_JUNGLE_WOOD, fn() => Blocks::JUNGLE_WOOD());
$this->mapSimple(Ids::JUNGLE_FENCE, fn() => Blocks::JUNGLE_FENCE());
$this->mapSimple(Ids::JUNGLE_PLANKS, fn() => Blocks::JUNGLE_PLANKS());
$this->mapSlab(Ids::JUNGLE_SLAB, Ids::JUNGLE_DOUBLE_SLAB, fn() => Blocks::JUNGLE_SLAB());
$this->mapStairs(Ids::JUNGLE_STAIRS, fn() => Blocks::JUNGLE_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::MANGROVE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::MANGROVE_BUTTON(), $in));
$this->map(Ids::MANGROVE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::MANGROVE_DOOR(), $in));
@ -571,10 +586,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->map(Ids::TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::OAK_TRAPDOOR(), $in));
$this->map(Ids::WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::OAK_WALL_SIGN(), $in));
$this->mapLog(Ids::OAK_LOG, Ids::STRIPPED_OAK_LOG, fn() => Blocks::OAK_LOG());
$this->mapLog(Ids::OAK_WOOD, Ids::STRIPPED_OAK_WOOD, fn() => Blocks::OAK_WOOD());
$this->mapSimple(Ids::OAK_FENCE, fn() => Blocks::OAK_FENCE());
$this->mapSimple(Ids::OAK_PLANKS, fn() => Blocks::OAK_PLANKS());
$this->mapSlab(Ids::OAK_SLAB, Ids::OAK_DOUBLE_SLAB, fn() => Blocks::OAK_SLAB());
$this->mapStairs(Ids::OAK_STAIRS, fn() => Blocks::OAK_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::SPRUCE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::SPRUCE_BUTTON(), $in));
$this->map(Ids::SPRUCE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::SPRUCE_DOOR(), $in));
@ -584,10 +600,11 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->map(Ids::SPRUCE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::SPRUCE_TRAPDOOR(), $in));
$this->map(Ids::SPRUCE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::SPRUCE_WALL_SIGN(), $in));
$this->mapLog(Ids::SPRUCE_LOG, Ids::STRIPPED_SPRUCE_LOG, fn() => Blocks::SPRUCE_LOG());
$this->mapLog(Ids::SPRUCE_WOOD, Ids::STRIPPED_SPRUCE_WOOD, fn() => Blocks::SPRUCE_WOOD());
$this->mapSimple(Ids::SPRUCE_FENCE, fn() => Blocks::SPRUCE_FENCE());
$this->mapSimple(Ids::SPRUCE_PLANKS, fn() => Blocks::SPRUCE_PLANKS());
$this->mapSlab(Ids::SPRUCE_SLAB, Ids::SPRUCE_DOUBLE_SLAB, fn() => Blocks::SPRUCE_SLAB());
$this->mapStairs(Ids::SPRUCE_STAIRS, fn() => Blocks::SPRUCE_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::WARPED_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::WARPED_BUTTON(), $in));
$this->map(Ids::WARPED_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::WARPED_DOOR(), $in));
@ -604,40 +621,30 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->mapStairs(Ids::WARPED_STAIRS, fn() => Blocks::WARPED_STAIRS());
}
private function registerLegacyWoodBlockDeserializers() : void{
$this->mapSlab(Ids::WOODEN_SLAB, Ids::DOUBLE_WOODEN_SLAB, fn(Reader $in) => Helper::mapWoodenSlabType($in));
$this->map(Ids::WOOD, fn(Reader $in) : Block => Helper::decodeLog(match($woodType = $in->readString(StateNames::WOOD_TYPE)){
StringValues::WOOD_TYPE_ACACIA => Blocks::ACACIA_WOOD(),
StringValues::WOOD_TYPE_BIRCH => Blocks::BIRCH_WOOD(),
StringValues::WOOD_TYPE_DARK_OAK => Blocks::DARK_OAK_WOOD(),
StringValues::WOOD_TYPE_JUNGLE => Blocks::JUNGLE_WOOD(),
StringValues::WOOD_TYPE_OAK => Blocks::OAK_WOOD(),
StringValues::WOOD_TYPE_SPRUCE => Blocks::SPRUCE_WOOD(),
default => throw $in->badValueException(StateNames::WOOD_TYPE, $woodType),
}, $in->readBool(StateNames::STRIPPED_BIT), $in));
}
private function registerLeavesDeserializers() : void{
//flattened IDs
$this->map(Ids::ACACIA_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::ACACIA_LEAVES(), $in));
$this->map(Ids::AZALEA_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::AZALEA_LEAVES(), $in));
$this->map(Ids::AZALEA_LEAVES_FLOWERED, fn(Reader $in) => Helper::decodeLeaves(Blocks::FLOWERING_AZALEA_LEAVES(), $in));
$this->map(Ids::BIRCH_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::BIRCH_LEAVES(), $in));
$this->map(Ids::CHERRY_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::CHERRY_LEAVES(), $in));
$this->map(Ids::DARK_OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::DARK_OAK_LEAVES(), $in));
$this->map(Ids::JUNGLE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::JUNGLE_LEAVES(), $in));
$this->map(Ids::MANGROVE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::MANGROVE_LEAVES(), $in));
$this->map(Ids::OAK_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::OAK_LEAVES(), $in));
$this->map(Ids::SPRUCE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::SPRUCE_LEAVES(), $in));
}
//legacy mess
$this->map(Ids::LEAVES, fn(Reader $in) => Helper::decodeLeaves(match($type = $in->readString(StateNames::OLD_LEAF_TYPE)){
StringValues::OLD_LEAF_TYPE_BIRCH => Blocks::BIRCH_LEAVES(),
StringValues::OLD_LEAF_TYPE_JUNGLE => Blocks::JUNGLE_LEAVES(),
StringValues::OLD_LEAF_TYPE_OAK => Blocks::OAK_LEAVES(),
StringValues::OLD_LEAF_TYPE_SPRUCE => Blocks::SPRUCE_LEAVES(),
default => throw $in->badValueException(StateNames::OLD_LEAF_TYPE, $type),
}, $in));
$this->map(Ids::LEAVES2, fn(Reader $in) => Helper::decodeLeaves(match($type = $in->readString(StateNames::NEW_LEAF_TYPE)){
StringValues::NEW_LEAF_TYPE_ACACIA => Blocks::ACACIA_LEAVES(),
StringValues::NEW_LEAF_TYPE_DARK_OAK => Blocks::DARK_OAK_LEAVES(),
default => throw $in->badValueException(StateNames::NEW_LEAF_TYPE, $type),
}, $in));
private function registerSaplingDeserializers() : void{
foreach([
Ids::ACACIA_SAPLING => fn() => Blocks::ACACIA_SAPLING(),
Ids::BIRCH_SAPLING => fn() => Blocks::BIRCH_SAPLING(),
Ids::DARK_OAK_SAPLING => fn() => Blocks::DARK_OAK_SAPLING(),
Ids::JUNGLE_SAPLING => fn() => Blocks::JUNGLE_SAPLING(),
Ids::OAK_SAPLING => fn() => Blocks::OAK_SAPLING(),
Ids::SPRUCE_SAPLING => fn() => Blocks::SPRUCE_SAPLING(),
] as $id => $getBlock){
$this->map($id, fn(Reader $in) => Helper::decodeSapling($getBlock(), $in));
}
}
private function registerSimpleDeserializers() : void{
@ -821,7 +828,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->mapSimple(Ids::GOLD_BLOCK, fn() => Blocks::GOLD());
$this->mapSimple(Ids::GOLD_ORE, fn() => Blocks::GOLD_ORE());
$this->mapSimple(Ids::GRANITE, fn() => Blocks::GRANITE());
$this->mapSimple(Ids::GRASS, fn() => Blocks::GRASS());
$this->mapSimple(Ids::GRASS_BLOCK, fn() => Blocks::GRASS());
$this->mapSimple(Ids::GRASS_PATH, fn() => Blocks::GRASS_PATH());
$this->mapSimple(Ids::GRAVEL, fn() => Blocks::GRAVEL());
$this->mapSimple(Ids::HANGING_ROOTS, fn() => Blocks::HANGING_ROOTS());
@ -898,6 +905,18 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->mapSimple(Ids::WEB, fn() => Blocks::COBWEB());
$this->mapSimple(Ids::WITHER_ROSE, fn() => Blocks::WITHER_ROSE());
$this->mapSimple(Ids::YELLOW_FLOWER, fn() => Blocks::DANDELION());
$this->mapSimple(Ids::ALLIUM, fn() => Blocks::ALLIUM());
$this->mapSimple(Ids::CORNFLOWER, fn() => Blocks::CORNFLOWER());
$this->mapSimple(Ids::AZURE_BLUET, fn() => Blocks::AZURE_BLUET());
$this->mapSimple(Ids::LILY_OF_THE_VALLEY, fn() => Blocks::LILY_OF_THE_VALLEY());
$this->mapSimple(Ids::BLUE_ORCHID, fn() => Blocks::BLUE_ORCHID());
$this->mapSimple(Ids::OXEYE_DAISY, fn() => Blocks::OXEYE_DAISY());
$this->mapSimple(Ids::POPPY, fn() => Blocks::POPPY());
$this->mapSimple(Ids::ORANGE_TULIP, fn() => Blocks::ORANGE_TULIP());
$this->mapSimple(Ids::PINK_TULIP, fn() => Blocks::PINK_TULIP());
$this->mapSimple(Ids::RED_TULIP, fn() => Blocks::RED_TULIP());
$this->mapSimple(Ids::WHITE_TULIP, fn() => Blocks::WHITE_TULIP());
}
private function registerDeserializers() : void{
@ -939,7 +958,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
});
});
$this->map(Ids::BAMBOO_SAPLING, function(Reader $in) : Block{
$in->ignored(StateNames::SAPLING_TYPE); //bug in MCPE
return Blocks::BAMBOO_SAPLING()->setReady($in->readBool(StateNames::AGE_BIT));
});
$this->map(Ids::BARREL, function(Reader $in) : Block{
@ -1096,10 +1114,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
->setCoralType($in->readCoralType())
->setDead($in->readBool(StateNames::DEAD_BIT));
});
$this->map(Ids::CORAL_FAN, fn(Reader $in) => Helper::decodeFloorCoralFan(Blocks::CORAL_FAN(), $in)
->setDead(false));
$this->map(Ids::CORAL_FAN_DEAD, fn(Reader $in) => Helper::decodeFloorCoralFan(Blocks::CORAL_FAN(), $in)
->setDead(true));
$this->map(Ids::CORAL_FAN_HANG, fn(Reader $in) => Helper::decodeWallCoralFan(Blocks::WALL_CORAL_FAN(), $in)
->setCoralType($in->readBool(StateNames::CORAL_HANG_TYPE_BIT) ? CoralType::BRAIN : CoralType::TUBE));
$this->map(Ids::CORAL_FAN_HANG2, fn(Reader $in) => Helper::decodeWallCoralFan(Blocks::WALL_CORAL_FAN(), $in)
@ -1431,22 +1445,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
return Blocks::RAIL()
->setShape($in->readBoundedInt(StateNames::RAIL_DIRECTION, 0, 9));
});
$this->map(Ids::RED_FLOWER, function(Reader $in) : Block{
return match($type = $in->readString(StateNames::FLOWER_TYPE)){
StringValues::FLOWER_TYPE_ALLIUM => Blocks::ALLIUM(),
StringValues::FLOWER_TYPE_CORNFLOWER => Blocks::CORNFLOWER(),
StringValues::FLOWER_TYPE_HOUSTONIA => Blocks::AZURE_BLUET(), //wtf ???
StringValues::FLOWER_TYPE_LILY_OF_THE_VALLEY => Blocks::LILY_OF_THE_VALLEY(),
StringValues::FLOWER_TYPE_ORCHID => Blocks::BLUE_ORCHID(),
StringValues::FLOWER_TYPE_OXEYE => Blocks::OXEYE_DAISY(),
StringValues::FLOWER_TYPE_POPPY => Blocks::POPPY(),
StringValues::FLOWER_TYPE_TULIP_ORANGE => Blocks::ORANGE_TULIP(),
StringValues::FLOWER_TYPE_TULIP_PINK => Blocks::PINK_TULIP(),
StringValues::FLOWER_TYPE_TULIP_RED => Blocks::RED_TULIP(),
StringValues::FLOWER_TYPE_TULIP_WHITE => Blocks::WHITE_TULIP(),
default => throw $in->badValueException(StateNames::FLOWER_TYPE, $type),
};
});
$this->map(Ids::RED_MUSHROOM_BLOCK, fn(Reader $in) => Helper::decodeMushroomBlock(Blocks::RED_MUSHROOM_BLOCK(), $in));
$this->mapStairs(Ids::RED_NETHER_BRICK_STAIRS, fn() => Blocks::RED_NETHER_BRICK_STAIRS());
$this->map(Ids::RED_SANDSTONE, function(Reader $in) : Block{
@ -1497,18 +1495,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
};
});
$this->mapStairs(Ids::SANDSTONE_STAIRS, fn() => Blocks::SANDSTONE_STAIRS());
$this->map(Ids::SAPLING, function(Reader $in) : Block{
return (match($type = $in->readString(StateNames::SAPLING_TYPE)){
StringValues::SAPLING_TYPE_ACACIA => Blocks::ACACIA_SAPLING(),
StringValues::SAPLING_TYPE_BIRCH => Blocks::BIRCH_SAPLING(),
StringValues::SAPLING_TYPE_DARK_OAK => Blocks::DARK_OAK_SAPLING(),
StringValues::SAPLING_TYPE_JUNGLE => Blocks::JUNGLE_SAPLING(),
StringValues::SAPLING_TYPE_OAK => Blocks::OAK_SAPLING(),
StringValues::SAPLING_TYPE_SPRUCE => Blocks::SPRUCE_SAPLING(),
default => throw $in->badValueException(StateNames::SAPLING_TYPE, $type),
})
->setReady($in->readBool(StateNames::AGE_BIT));
});
$this->map(Ids::SEA_PICKLE, function(Reader $in) : Block{
return Blocks::SEA_PICKLE()
->setCount($in->readBoundedInt(StateNames::CLUSTER_COUNT, 0, 3) + 1)

View File

@ -27,7 +27,6 @@ use pocketmine\block\utils\BellAttachmentType;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\SlabType;
use pocketmine\block\utils\WallConnectionType;
use pocketmine\block\utils\WoodType;
use pocketmine\data\bedrock\block\BlockLegacyMetadata;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\data\bedrock\block\BlockStateNames;
@ -257,20 +256,6 @@ final class BlockStateWriter{
return $this;
}
/** @return $this */
public function writeLegacyWoodType(WoodType $treeType) : self{
$this->writeString(BlockStateNames::WOOD_TYPE, match($treeType){
WoodType::OAK => StringValues::WOOD_TYPE_OAK,
WoodType::SPRUCE => StringValues::WOOD_TYPE_SPRUCE,
WoodType::BIRCH => StringValues::WOOD_TYPE_BIRCH,
WoodType::JUNGLE => StringValues::WOOD_TYPE_JUNGLE,
WoodType::ACACIA => StringValues::WOOD_TYPE_ACACIA,
WoodType::DARK_OAK => StringValues::WOOD_TYPE_DARK_OAK,
default => throw new BlockStateSerializeException("Invalid legacy wood type " . $treeType->name)
});
return $this;
}
/** @return $this */
public function writeCoralType(CoralType $coralType) : self{
$this->writeString(BlockStateNames::CORAL_COLOR, match($coralType){

View File

@ -64,20 +64,24 @@ final class BlockStateUpgradeSchema{
*/
public array $remappedStates = [];
public readonly int $versionId;
public function __construct(
public int $maxVersionMajor,
public int $maxVersionMinor,
public int $maxVersionPatch,
public int $maxVersionRevision,
public readonly int $maxVersionMajor,
public readonly int $maxVersionMinor,
public readonly int $maxVersionPatch,
public readonly int $maxVersionRevision,
private int $schemaId
){}
){
$this->versionId = ($this->maxVersionMajor << 24) | ($this->maxVersionMinor << 16) | ($this->maxVersionPatch << 8) | $this->maxVersionRevision;
}
/**
* @deprecated This is defined by Mojang, and therefore cannot be relied on. Use getSchemaId() instead for
* internal version management.
*/
public function getVersionId() : int{
return ($this->maxVersionMajor << 24) | ($this->maxVersionMinor << 16) | ($this->maxVersionPatch << 8) | $this->maxVersionRevision;
return $this->versionId;
}
public function getSchemaId() : int{ return $this->schemaId; }

View File

@ -23,17 +23,28 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock\block\upgrade;
use function ksort;
use const SORT_STRING;
final class BlockStateUpgradeSchemaFlattenedName{
/**
* @param string[] $flattenedValueRemaps
* @phpstan-param array<string, string> $flattenedValueRemaps
*/
public function __construct(
public string $prefix,
public string $flattenedProperty,
public string $suffix
){}
public string $suffix,
public array $flattenedValueRemaps
){
ksort($this->flattenedValueRemaps, SORT_STRING);
}
public function equals(self $that) : bool{
return $this->prefix === $that->prefix &&
$this->flattenedProperty === $that->flattenedProperty &&
$this->suffix === $that->suffix;
$this->suffix === $that->suffix &&
$this->flattenedValueRemaps === $that->flattenedValueRemaps;
}
}

View File

@ -166,7 +166,8 @@ final class BlockStateUpgradeSchemaUtils{
$remap->newName ?? new BlockStateUpgradeSchemaFlattenedName(
$remap->newFlattenedName->prefix,
$remap->newFlattenedName->flattenedProperty,
$remap->newFlattenedName->suffix
$remap->newFlattenedName->suffix,
$remap->newFlattenedName->flattenedValueRemaps ?? [],
),
array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->newState ?? []),
$remap->copiedState ?? []
@ -301,7 +302,8 @@ final class BlockStateUpgradeSchemaUtils{
new BlockStateUpgradeSchemaModelFlattenedName(
$remap->newName->prefix,
$remap->newName->flattenedProperty,
$remap->newName->suffix
$remap->newName->suffix,
$remap->newName->flattenedValueRemaps
),
array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->newState),
$remap->copiedState

View File

@ -35,9 +35,14 @@ use function sprintf;
use const SORT_NUMERIC;
final class BlockStateUpgrader{
/** @var BlockStateUpgradeSchema[] */
/**
* @var BlockStateUpgradeSchema[][] versionId => [schemaId => schema]
* @phpstan-var array<int, array<int, BlockStateUpgradeSchema>>
*/
private array $upgradeSchemas = [];
private int $outputVersion = 0;
/**
* @param BlockStateUpgradeSchema[] $upgradeSchemas
* @phpstan-param array<int, BlockStateUpgradeSchema> $upgradeSchemas
@ -50,87 +55,116 @@ final class BlockStateUpgrader{
public function addSchema(BlockStateUpgradeSchema $schema) : void{
$schemaId = $schema->getSchemaId();
if(isset($this->upgradeSchemas[$schemaId])){
throw new \InvalidArgumentException("Cannot add two schemas with the same schema ID");
$versionId = $schema->getVersionId();
if(isset($this->upgradeSchemas[$versionId][$schemaId])){
throw new \InvalidArgumentException("Cannot add two schemas with the same schema ID and version ID");
}
$this->upgradeSchemas[$schemaId] = $schema;
//schema ID tells us the order when multiple schemas use the same version ID
$this->upgradeSchemas[$versionId][$schemaId] = $schema;
ksort($this->upgradeSchemas, SORT_NUMERIC);
ksort($this->upgradeSchemas[$versionId], SORT_NUMERIC);
$this->outputVersion = max($this->outputVersion, $schema->getVersionId());
}
public function upgrade(BlockStateData $blockStateData) : BlockStateData{
$version = $blockStateData->getVersion();
$highestVersion = $version;
foreach($this->upgradeSchemas as $schema){
$resultVersion = $schema->getVersionId();
$highestVersion = max($highestVersion, $resultVersion);
if($version > $resultVersion){
//even if this is actually the same version, we have to apply it anyway because mojang are dumb and
//didn't always bump the blockstate version when changing it :(
foreach($this->upgradeSchemas as $resultVersion => $schemaList){
/*
* Sometimes Mojang made changes without bumping the version ID.
* A notable example is 0131_1.18.20.27_beta_to_1.18.30.json, which renamed a bunch of blockIDs.
* When this happens, all the schemas must be applied even if the version is the same, because the input
* version doesn't tell us which of the schemas have already been applied.
* If there's only one schema for a version (the norm), we can safely assume it's already been applied if
* the version is the same, and skip over it.
*/
if($version > $resultVersion || (count($schemaList) === 1 && $version === $resultVersion)){
continue;
}
$oldName = $blockStateData->getName();
$oldState = $blockStateData->getStates();
if(isset($schema->remappedStates[$oldName])){
foreach($schema->remappedStates[$oldName] as $remap){
if(count($remap->oldState) > count($oldState)){
//match criteria has more requirements than we have state properties
continue; //try next state
}
foreach(Utils::stringifyKeys($remap->oldState) as $k => $v){
if(!isset($oldState[$k]) || !$oldState[$k]->equals($v)){
continue 2; //try next state
}
}
if(is_string($remap->newName)){
$newName = $remap->newName;
}else{
$flattenedValue = $oldState[$remap->newName->flattenedProperty] ?? null;
if($flattenedValue instanceof StringTag){
$newName = sprintf("%s%s%s", $remap->newName->prefix, $flattenedValue->getValue(), $remap->newName->suffix);
unset($oldState[$remap->newName->flattenedProperty]);
}else{
//flattened property is not a TAG_String, so this transformation is not applicable
continue;
}
}
$newState = $remap->newState;
foreach($remap->copiedState as $stateName){
if(isset($oldState[$stateName])){
$newState[$stateName] = $oldState[$stateName];
}
}
$blockStateData = new BlockStateData($newName, $newState, $resultVersion);
continue 2; //try next schema
}
}
$newName = $schema->renamedIds[$oldName] ?? null;
$stateChanges = 0;
$states = $blockStateData->getStates();
$states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges);
$states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges);
$states = $this->applyPropertyRenamedOrValueChanged($schema, $oldName, $states, $stateChanges);
$states = $this->applyPropertyValueChanged($schema, $oldName, $states, $stateChanges);
if($newName !== null || $stateChanges > 0){
$blockStateData = new BlockStateData($newName ?? $oldName, $states, $resultVersion);
//don't break out; we may need to further upgrade the state
foreach($schemaList as $schema){
$blockStateData = $this->applySchema($schema, $blockStateData);
}
}
if($highestVersion > $version){
if($this->outputVersion > $version){
//always update the version number of the blockstate, even if it didn't change - this is needed for
//external tools
$blockStateData = new BlockStateData($blockStateData->getName(), $blockStateData->getStates(), $highestVersion);
$blockStateData = new BlockStateData($blockStateData->getName(), $blockStateData->getStates(), $this->outputVersion);
}
return $blockStateData;
}
private function applySchema(BlockStateUpgradeSchema $schema, BlockStateData $blockStateData) : BlockStateData{
$newStateData = $this->applyStateRemapped($schema, $blockStateData);
if($newStateData !== null){
return $newStateData;
}
$oldName = $blockStateData->getName();
$newName = $schema->renamedIds[$oldName] ?? null;
$stateChanges = 0;
$states = $blockStateData->getStates();
$states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges);
$states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges);
$states = $this->applyPropertyRenamedOrValueChanged($schema, $oldName, $states, $stateChanges);
$states = $this->applyPropertyValueChanged($schema, $oldName, $states, $stateChanges);
if($newName !== null || $stateChanges > 0){
return new BlockStateData($newName ?? $oldName, $states, $schema->getVersionId());
}
return $blockStateData;
}
private function applyStateRemapped(BlockStateUpgradeSchema $schema, BlockStateData $blockStateData) : ?BlockStateData{
$oldName = $blockStateData->getName();
$oldState = $blockStateData->getStates();
if(isset($schema->remappedStates[$oldName])){
foreach($schema->remappedStates[$oldName] as $remap){
if(count($remap->oldState) > count($oldState)){
//match criteria has more requirements than we have state properties
continue; //try next state
}
foreach(Utils::stringifyKeys($remap->oldState) as $k => $v){
if(!isset($oldState[$k]) || !$oldState[$k]->equals($v)){
continue 2; //try next state
}
}
if(is_string($remap->newName)){
$newName = $remap->newName;
}else{
$flattenedValue = $oldState[$remap->newName->flattenedProperty] ?? null;
if($flattenedValue instanceof StringTag){
$embedValue = $remap->newName->flattenedValueRemaps[$flattenedValue->getValue()] ?? $flattenedValue->getValue();
$newName = sprintf("%s%s%s", $remap->newName->prefix, $embedValue, $remap->newName->suffix);
unset($oldState[$remap->newName->flattenedProperty]);
}else{
//flattened property is not a TAG_String, so this transformation is not applicable
continue;
}
}
$newState = $remap->newState;
foreach($remap->copiedState as $stateName){
if(isset($oldState[$stateName])){
$newState[$stateName] = $oldState[$stateName];
}
}
return new BlockStateData($newName, $newState, $schema->getVersionId());
}
}
return null;
}
/**
* @param Tag[] $states
* @phpstan-param array<string, Tag> $states

View File

@ -23,7 +23,9 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock\block\upgrade\model;
final class BlockStateUpgradeSchemaModelFlattenedName{
use function count;
final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializable{
/** @required */
public string $prefix;
@ -31,10 +33,31 @@ final class BlockStateUpgradeSchemaModelFlattenedName{
public string $flattenedProperty;
/** @required */
public string $suffix;
/**
* @var string[]
* @phpstan-var array<string, string>
*/
public array $flattenedValueRemaps;
public function __construct(string $prefix, string $flattenedProperty, string $suffix){
/**
* @param string[] $flattenedValueRemaps
* @phpstan-param array<string, string> $flattenedValueRemaps
*/
public function __construct(string $prefix, string $flattenedProperty, string $suffix, array $flattenedValueRemaps){
$this->prefix = $prefix;
$this->flattenedProperty = $flattenedProperty;
$this->suffix = $suffix;
$this->flattenedValueRemaps = $flattenedValueRemaps;
}
/**
* @return mixed[]
*/
public function jsonSerialize() : array{
$result = (array) $this;
if(count($this->flattenedValueRemaps) === 0){
unset($result["flattenedValueRemaps"]);
}
return $result;
}
}

View File

@ -305,6 +305,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::MUSIC_DISC_WAIT, Items::RECORD_WAIT());
$this->map1to1Item(Ids::MUSIC_DISC_WARD, Items::RECORD_WARD());
$this->map1to1Item(Ids::MUTTON, Items::RAW_MUTTON());
$this->map1to1Item(Ids::NAME_TAG, Items::NAME_TAG());
$this->map1to1Item(Ids::NAUTILUS_SHELL, Items::NAUTILUS_SHELL());
$this->map1to1Item(Ids::NETHER_STAR, Items::NETHER_STAR());
$this->map1to1Item(Ids::NETHERBRICK, Items::NETHER_BRICK());

View File

@ -74,6 +74,8 @@ final class ItemTypeNames{
public const BLEACH = "minecraft:bleach";
public const BLUE_DYE = "minecraft:blue_dye";
public const BOAT = "minecraft:boat";
public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg";
public const BOLT_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:bolt_armor_trim_smithing_template";
public const BONE = "minecraft:bone";
public const BONE_MEAL = "minecraft:bone_meal";
public const BOOK = "minecraft:book";
@ -81,6 +83,7 @@ final class ItemTypeNames{
public const BOW = "minecraft:bow";
public const BOWL = "minecraft:bowl";
public const BREAD = "minecraft:bread";
public const BREEZE_ROD = "minecraft:breeze_rod";
public const BREEZE_SPAWN_EGG = "minecraft:breeze_spawn_egg";
public const BREWER_POTTERY_SHERD = "minecraft:brewer_pottery_sherd";
public const BREWING_STAND = "minecraft:brewing_stand";
@ -140,6 +143,8 @@ final class ItemTypeNames{
public const COPPER_DOOR = "minecraft:copper_door";
public const COPPER_INGOT = "minecraft:copper_ingot";
public const CORAL = "minecraft:coral";
public const CORAL_FAN = "minecraft:coral_fan";
public const CORAL_FAN_DEAD = "minecraft:coral_fan_dead";
public const COW_SPAWN_EGG = "minecraft:cow_spawn_egg";
public const CREEPER_BANNER_PATTERN = "minecraft:creeper_banner_pattern";
public const CREEPER_SPAWN_EGG = "minecraft:creeper_spawn_egg";
@ -154,7 +159,6 @@ final class ItemTypeNames{
public const DARK_OAK_DOOR = "minecraft:dark_oak_door";
public const DARK_OAK_HANGING_SIGN = "minecraft:dark_oak_hanging_sign";
public const DARK_OAK_SIGN = "minecraft:dark_oak_sign";
public const DEBUG_STICK = "minecraft:debug_stick";
public const DIAMOND = "minecraft:diamond";
public const DIAMOND_AXE = "minecraft:diamond_axe";
public const DIAMOND_BOOTS = "minecraft:diamond_boots";
@ -204,6 +208,9 @@ final class ItemTypeNames{
public const FISHING_ROD = "minecraft:fishing_rod";
public const FLINT = "minecraft:flint";
public const FLINT_AND_STEEL = "minecraft:flint_and_steel";
public const FLOW_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:flow_armor_trim_smithing_template";
public const FLOW_BANNER_PATTERN = "minecraft:flow_banner_pattern";
public const FLOW_POTTERY_SHERD = "minecraft:flow_pottery_sherd";
public const FLOWER_BANNER_PATTERN = "minecraft:flower_banner_pattern";
public const FLOWER_POT = "minecraft:flower_pot";
public const FOX_SPAWN_EGG = "minecraft:fox_spawn_egg";
@ -241,6 +248,8 @@ final class ItemTypeNames{
public const GREEN_DYE = "minecraft:green_dye";
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 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";
@ -286,6 +295,8 @@ final class ItemTypeNames{
public const LEATHER_HELMET = "minecraft:leather_helmet";
public const LEATHER_HORSE_ARMOR = "minecraft:leather_horse_armor";
public const LEATHER_LEGGINGS = "minecraft:leather_leggings";
public const LEAVES = "minecraft:leaves";
public const LEAVES2 = "minecraft:leaves2";
public const LIGHT_BLUE_DYE = "minecraft:light_blue_dye";
public const LIGHT_GRAY_DYE = "minecraft:light_gray_dye";
public const LIME_DYE = "minecraft:lime_dye";
@ -294,6 +305,7 @@ final class ItemTypeNames{
public const LODESTONE_COMPASS = "minecraft:lodestone_compass";
public const LOG = "minecraft:log";
public const LOG2 = "minecraft:log2";
public const MACE = "minecraft:mace";
public const MAGENTA_DYE = "minecraft:magenta_dye";
public const MAGMA_CREAM = "minecraft:magma_cream";
public const MAGMA_CUBE_SPAWN_EGG = "minecraft:magma_cube_spawn_egg";
@ -401,6 +413,7 @@ final class ItemTypeNames{
public const RAW_IRON = "minecraft:raw_iron";
public const RECOVERY_COMPASS = "minecraft:recovery_compass";
public const RED_DYE = "minecraft:red_dye";
public const RED_FLOWER = "minecraft:red_flower";
public const REDSTONE = "minecraft:redstone";
public const REPEATER = "minecraft:repeater";
public const RIB_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:rib_armor_trim_smithing_template";
@ -409,6 +422,8 @@ final class ItemTypeNames{
public const SALMON = "minecraft:salmon";
public const SALMON_BUCKET = "minecraft:salmon_bucket";
public const SALMON_SPAWN_EGG = "minecraft:salmon_spawn_egg";
public const SAPLING = "minecraft:sapling";
public const SCRAPE_POTTERY_SHERD = "minecraft:scrape_pottery_sherd";
public const SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:sentry_armor_trim_smithing_template";
public const SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:shaper_armor_trim_smithing_template";
public const SHEAF_POTTERY_SHERD = "minecraft:sheaf_pottery_sherd";
@ -500,16 +515,19 @@ final class ItemTypeNames{
public const WHEAT_SEEDS = "minecraft:wheat_seeds";
public const WHITE_DYE = "minecraft:white_dye";
public const WILD_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:wild_armor_trim_smithing_template";
public const WIND_CHARGE = "minecraft:wind_charge";
public const WITCH_SPAWN_EGG = "minecraft:witch_spawn_egg";
public const WITHER_SKELETON_SPAWN_EGG = "minecraft:wither_skeleton_spawn_egg";
public const WITHER_SPAWN_EGG = "minecraft:wither_spawn_egg";
public const WOLF_ARMOR = "minecraft:wolf_armor";
public const WOLF_SPAWN_EGG = "minecraft:wolf_spawn_egg";
public const WOOD = "minecraft:wood";
public const WOODEN_AXE = "minecraft:wooden_axe";
public const WOODEN_DOOR = "minecraft:wooden_door";
public const WOODEN_HOE = "minecraft:wooden_hoe";
public const WOODEN_PICKAXE = "minecraft:wooden_pickaxe";
public const WOODEN_SHOVEL = "minecraft:wooden_shovel";
public const WOODEN_SLAB = "minecraft:wooden_slab";
public const WOODEN_SWORD = "minecraft:wooden_sword";
public const WOOL = "minecraft:wool";
public const WRITABLE_BOOK = "minecraft:writable_book";

View File

@ -44,10 +44,14 @@ final class GameModeIdMap{
private array $enumToId = [];
public function __construct(){
$this->register(0, GameMode::SURVIVAL);
$this->register(1, GameMode::CREATIVE);
$this->register(2, GameMode::ADVENTURE);
$this->register(3, GameMode::SPECTATOR);
foreach(GameMode::cases() as $case){
$this->register(match($case){
GameMode::SURVIVAL => 0,
GameMode::CREATIVE => 1,
GameMode::ADVENTURE => 2,
GameMode::SPECTATOR => 3,
}, $case);
}
}
private function register(int $id, GameMode $type) : void{

View File

@ -29,11 +29,6 @@ namespace pocketmine\data\runtime;
* @deprecated
*/
trait LegacyRuntimeEnumDescriberTrait{
/**
* @phpstan-template T of \UnitEnum
* @phpstan-param T $case
*/
abstract protected function enum(\UnitEnum &$case) : void;
public function bellAttachmentType(\pocketmine\block\utils\BellAttachmentType &$value) : void{

View File

@ -89,10 +89,6 @@ interface RuntimeDataDescriber extends RuntimeEnumDescriber{
public function straightOnlyRailShape(int &$railShape) : void;
/**
* @phpstan-template T of \UnitEnum
* @phpstan-param T $case
*/
public function enum(\UnitEnum &$case) : void;
/**

View File

@ -252,6 +252,14 @@ abstract class Entity{
return $this->alwaysShowNameTag;
}
/**
* Returns whether players can rename this entity using a name tag.
* Note that plugins can still name entities using setNameTag().
*/
public function canBeRenamed() : bool{
return false;
}
public function setNameTag(string $name) : void{
$this->nameTag = $name;
$this->networkPropertiesDirty = true;
@ -792,7 +800,7 @@ abstract class Entity{
}
protected function broadcastMotion() : void{
NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion())]);
NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion(), tick: 0)]);
}
public function getGravity() : float{

View File

@ -132,6 +132,10 @@ abstract class Living extends Entity{
abstract public function getName() : string;
public function canBeRenamed() : bool{
return true;
}
protected function initEntity(CompoundTag $nbt) : void{
parent::initEntity($nbt);

View File

@ -37,8 +37,7 @@ class HandlerList{
private array $affectedHandlerCaches = [];
/**
* @phpstan-template TEvent of Event
* @phpstan-param class-string<TEvent> $class
* @phpstan-param class-string<covariant Event> $class
*/
public function __construct(
private string $class,

View File

@ -86,8 +86,7 @@ class HandlerListManager{
*
* Calling this method also lazily initializes the $classMap inheritance tree of handler lists.
*
* @phpstan-template TEvent of Event
* @phpstan-param class-string<TEvent> $event
* @phpstan-param class-string<covariant Event> $event
*
* @throws \ReflectionException
* @throws \InvalidArgumentException
@ -113,8 +112,7 @@ class HandlerListManager{
}
/**
* @phpstan-template TEvent of Event
* @phpstan-param class-string<TEvent> $event
* @phpstan-param class-string<covariant Event> $event
*
* @return RegisteredListener[]
*/

View File

@ -0,0 +1,106 @@
<?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\event\player;
use pocketmine\event\Event;
use pocketmine\player\PlayerInfo;
use pocketmine\resourcepacks\ResourcePack;
use function array_unshift;
/**
* Called after a player authenticates and is being offered resource packs to download.
*
* This event should be used to decide which resource packs to offer the player and whether to require the player to
* download the packs before they can join the server.
*/
class PlayerResourcePackOfferEvent extends Event{
/**
* @param ResourcePack[] $resourcePacks
* @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted
*
* @phpstan-param list<ResourcePack> $resourcePacks
* @phpstan-param array<string, string> $encryptionKeys
*/
public function __construct(
private readonly PlayerInfo $playerInfo,
private array $resourcePacks,
private array $encryptionKeys,
private bool $mustAccept
){}
public function getPlayerInfo() : PlayerInfo{
return $this->playerInfo;
}
/**
* Adds a resource pack to the top of the stack.
* The resources in this pack will be applied over the top of any existing packs.
*/
public function addResourcePack(ResourcePack $entry, ?string $encryptionKey = null) : void{
array_unshift($this->resourcePacks, $entry);
if($encryptionKey !== null){
$this->encryptionKeys[$entry->getPackId()] = $encryptionKey;
}
}
/**
* Sets the resource packs to offer. Packs are applied from the highest key to the lowest, with each pack
* overwriting any resources from the previous pack. This means that the pack at index 0 gets the final say on which
* resources are used.
*
* @param ResourcePack[] $resourcePacks
* @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted
*
* @phpstan-param list<ResourcePack> $resourcePacks
* @phpstan-param array<string, string> $encryptionKeys
*/
public function setResourcePacks(array $resourcePacks, array $encryptionKeys) : void{
$this->resourcePacks = $resourcePacks;
$this->encryptionKeys = $encryptionKeys;
}
/**
* @return ResourcePack[]
* @phpstan-return list<ResourcePack>
*/
public function getResourcePacks() : array{
return $this->resourcePacks;
}
/**
* @return string[]
* @phpstan-return array<string, string>
*/
public function getEncryptionKeys() : array{
return $this->encryptionKeys;
}
public function setMustAccept(bool $mustAccept) : void{
$this->mustAccept = $mustAccept;
}
public function mustAccept() : bool{
return $this->mustAccept;
}
}

View File

@ -146,6 +146,10 @@ class Armor extends Durable{
$new = $thisCopy->pop();
$player->getArmorInventory()->setItem($this->getArmorSlot(), $new);
$player->getInventory()->setItemInHand($existing);
$sound = $new->getMaterial()->getEquipSound();
if($sound !== null){
$player->broadcastSound($sound);
}
if(!$thisCopy->isNull()){
//if the stack size was bigger than 1 (usually won't happen, but might be caused by plugins)
$returnedItems[] = $thisCopy;

View File

@ -23,10 +23,13 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\world\sound\Sound;
class ArmorMaterial{
public function __construct(
private readonly int $enchantability
private readonly int $enchantability,
private readonly ?Sound $equipSound = null
){
}
@ -39,4 +42,11 @@ class ArmorMaterial{
public function getEnchantability() : int{
return $this->enchantability;
}
/**
* Returns the sound that plays when equipping the armor
*/
public function getEquipSound() : ?Sound{
return $this->equipSound;
}
}

View File

@ -323,8 +323,9 @@ final class ItemTypeIds{
public const EYE_ARMOR_TRIM_SMITHING_TEMPLATE = 20284;
public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = 20285;
public const PITCHER_POD = 20286;
public const NAME_TAG = 20287;
public const FIRST_UNUSED_ITEM_ID = 20287;
public const FIRST_UNUSED_ITEM_ID = 20288;
private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;

40
src/item/NameTag.php Normal file
View File

@ -0,0 +1,40 @@
<?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\item;
use pocketmine\entity\Entity;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
class NameTag extends Item{
public function onInteractEntity(Player $player, Entity $entity, Vector3 $clickVector) : bool{
if($entity->canBeRenamed() && $this->hasCustomName()){
$entity->setNameTag($this->getCustomName());
$this->pop();
return true;
}
return false;
}
}

View File

@ -1384,6 +1384,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("mutton_raw", fn() => Items::RAW_MUTTON());
$result->register("muttoncooked", fn() => Items::COOKED_MUTTON());
$result->register("muttonraw", fn() => Items::RAW_MUTTON());
$result->register("name_tag", fn() => Items::NAME_TAG());
$result->register("nautilus_shell", fn() => Items::NAUTILUS_SHELL());
$result->register("nether_brick", fn() => Items::NETHER_BRICK());
$result->register("nether_quartz", fn() => Items::NETHER_QUARTZ());

View File

@ -24,6 +24,13 @@ declare(strict_types=1);
namespace pocketmine\item;
use pocketmine\utils\RegistryTrait;
use pocketmine\world\sound\ArmorEquipChainSound;
use pocketmine\world\sound\ArmorEquipDiamondSound;
use pocketmine\world\sound\ArmorEquipGenericSound;
use pocketmine\world\sound\ArmorEquipGoldSound;
use pocketmine\world\sound\ArmorEquipIronSound;
use pocketmine\world\sound\ArmorEquipLeatherSound;
use pocketmine\world\sound\ArmorEquipNetheriteSound;
/**
* This doc-block is generated automatically, do not modify it manually.
@ -62,12 +69,12 @@ final class VanillaArmorMaterials{
}
protected static function setup() : void{
self::register("leather", new ArmorMaterial(15));
self::register("chainmail", new ArmorMaterial(12));
self::register("iron", new ArmorMaterial(9));
self::register("turtle", new ArmorMaterial(9));
self::register("gold", new ArmorMaterial(25));
self::register("diamond", new ArmorMaterial(10));
self::register("netherite", new ArmorMaterial(15));
self::register("leather", new ArmorMaterial(15, new ArmorEquipLeatherSound()));
self::register("chainmail", new ArmorMaterial(12, new ArmorEquipChainSound()));
self::register("iron", new ArmorMaterial(9, new ArmorEquipIronSound()));
self::register("turtle", new ArmorMaterial(9, new ArmorEquipGenericSound()));
self::register("gold", new ArmorMaterial(25, new ArmorEquipGoldSound()));
self::register("diamond", new ArmorMaterial(10, new ArmorEquipDiamondSound()));
self::register("netherite", new ArmorMaterial(15, new ArmorEquipNetheriteSound()));
}
}

View File

@ -219,6 +219,7 @@ use function strtolower;
* @method static MilkBucket MILK_BUCKET()
* @method static Minecart MINECART()
* @method static MushroomStew MUSHROOM_STEW()
* @method static NameTag NAME_TAG()
* @method static Item NAUTILUS_SHELL()
* @method static Axe NETHERITE_AXE()
* @method static Armor NETHERITE_BOOTS()
@ -490,6 +491,7 @@ final class VanillaItems{
self::register("milk_bucket", new MilkBucket(new IID(Ids::MILK_BUCKET), "Milk Bucket"));
self::register("minecart", new Minecart(new IID(Ids::MINECART), "Minecart"));
self::register("mushroom_stew", new MushroomStew(new IID(Ids::MUSHROOM_STEW), "Mushroom Stew"));
self::register("name_tag", new NameTag(new IID(Ids::NAME_TAG), "Name Tag"));
self::register("nautilus_shell", new Item(new IID(Ids::NAUTILUS_SHELL), "Nautilus Shell"));
self::register("nether_brick", new Item(new IID(Ids::NETHER_BRICK), "Nether Brick"));
self::register("nether_quartz", new Item(new IID(Ids::NETHER_QUARTZ), "Nether Quartz"));

View File

@ -28,7 +28,6 @@ use pocketmine\network\mcpe\compression\Compressor;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\LevelChunkPacket;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\protocol\types\ChunkPosition;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\serializer\ChunkSerializer;
@ -72,11 +71,10 @@ class ChunkRequestTask extends AsyncTask{
$subCount = ChunkSerializer::getSubChunkCount($chunk, $dimensionId);
$converter = TypeConverter::getInstance();
$encoderContext = new PacketSerializerContext($converter->getItemTypeDictionary());
$payload = ChunkSerializer::serializeFullChunk($chunk, $dimensionId, $converter->getBlockTranslator(), $encoderContext, $this->tiles);
$payload = ChunkSerializer::serializeFullChunk($chunk, $dimensionId, $converter->getBlockTranslator(), $this->tiles);
$stream = new BinaryStream();
PacketBatch::encodePackets($stream, $encoderContext, [LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $dimensionId, $subCount, false, null, $payload)]);
PacketBatch::encodePackets($stream, [LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $dimensionId, $subCount, false, null, $payload)]);
$compressor = $this->compressor->deserialize();
$this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($stream->getBuffer()));

View File

@ -423,6 +423,41 @@ class InventoryManager{
}
}
/**
* Compares itemstack extra data for equality. This is used to verify legacy InventoryTransaction slot predictions.
*
* TODO: It would be preferable if we didn't have to deserialize this, to improve performance and reduce attack
* surface. However, the raw data may not match due to differences in ordering. Investigate whether the
* client-provided NBT is consistently sorted.
*/
private function itemStackExtraDataEqual(ItemStack $left, ItemStack $right) : bool{
if($left->getRawExtraData() === $right->getRawExtraData()){
return true;
}
$typeConverter = $this->session->getTypeConverter();
$leftExtraData = $typeConverter->deserializeItemStackExtraData($left->getRawExtraData(), $left->getId());
$rightExtraData = $typeConverter->deserializeItemStackExtraData($right->getRawExtraData(), $right->getId());
$leftNbt = $leftExtraData->getNbt();
$rightNbt = $rightExtraData->getNbt();
return
$leftExtraData->getCanPlaceOn() === $rightExtraData->getCanPlaceOn() &&
$leftExtraData->getCanDestroy() === $rightExtraData->getCanDestroy() && (
$leftNbt === $rightNbt || //this covers null === null and fast object identity
($leftNbt !== null && $rightNbt !== null && $leftNbt->equals($rightNbt))
);
}
private function itemStacksEqual(ItemStack $left, ItemStack $right) : bool{
return
$left->getId() === $right->getId() &&
$left->getMeta() === $right->getMeta() &&
$left->getBlockRuntimeId() === $right->getBlockRuntimeId() &&
$left->getCount() === $right->getCount() &&
$this->itemStackExtraDataEqual($left, $right);
}
public function onSlotChange(Inventory $inventory, int $slot) : void{
$inventoryEntry = $this->inventories[spl_object_id($inventory)] ?? null;
if($inventoryEntry === null){
@ -432,7 +467,7 @@ class InventoryManager{
}
$currentItem = $this->session->getTypeConverter()->coreItemStackToNet($inventory->getItem($slot));
$clientSideItem = $inventoryEntry->predictions[$slot] ?? null;
if($clientSideItem === null || !$clientSideItem->equals($currentItem)){
if($clientSideItem === null || !$this->itemStacksEqual($currentItem, $clientSideItem)){
//no prediction or incorrect - do not associate this with the currently active itemstack request
$this->trackItemStack($inventoryEntry, $slot, $currentItem, null);
$inventoryEntry->pendingSyncs[$slot] = $currentItem;

View File

@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe;
use pocketmine\entity\effect\EffectInstance;
use pocketmine\event\player\PlayerDuplicateLoginEvent;
use pocketmine\event\player\PlayerResourcePackOfferEvent;
use pocketmine\event\server\DataPacketDecodeEvent;
use pocketmine\event\server\DataPacketReceiveEvent;
use pocketmine\event\server\DataPacketSendEvent;
@ -67,7 +68,6 @@ use pocketmine\network\mcpe\protocol\PlayStatusPacket;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\protocol\ServerboundPacket;
use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket;
use pocketmine\network\mcpe\protocol\SetDifficultyPacket;
@ -101,6 +101,8 @@ use pocketmine\player\Player;
use pocketmine\player\PlayerInfo;
use pocketmine\player\UsedChunkStatus;
use pocketmine\player\XboxLivePlayerInfo;
use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
@ -159,15 +161,24 @@ class NetworkSession{
/** @var string[] */
private array $sendBuffer = [];
/**
* @var \SplQueue|CompressBatchPromise[]|string[]
* @phpstan-var \SplQueue<CompressBatchPromise|string>
* @var PromiseResolver[]
* @phpstan-var list<PromiseResolver<true>>
*/
private array $sendBufferAckPromises = [];
/** @phpstan-var \SplQueue<array{CompressBatchPromise|string, list<PromiseResolver<true>>}> */
private \SplQueue $compressedQueue;
private bool $forceAsyncCompression = true;
private bool $enableCompression = false; //disabled until handshake completed
private int $nextAckReceiptId = 0;
/**
* @var PromiseResolver[][]
* @phpstan-var array<int, list<PromiseResolver<true>>>
*/
private array $ackPromisesByReceiptId = [];
private ?InventoryManager $invManager = null;
/**
@ -180,7 +191,6 @@ class NetworkSession{
private Server $server,
private NetworkSessionManager $manager,
private PacketPool $packetPool,
private PacketSerializerContext $packetSerializerContext,
private PacketSender $sender,
private PacketBroadcaster $broadcaster,
private EntityEventBroadcaster $entityEventBroadcaster,
@ -362,12 +372,12 @@ class NetworkSession{
}
if($this->enableCompression){
Timings::$playerNetworkReceiveDecompress->startTiming();
$compressionType = ord($payload[0]);
$compressed = substr($payload, 1);
if($compressionType === CompressionAlgorithm::NONE){
$decompressed = $compressed;
}elseif($compressionType === $this->compressor->getNetworkId()){
Timings::$playerNetworkReceiveDecompress->startTiming();
try{
$decompressed = $this->compressor->decompress($compressed);
}catch(DecompressionException $e){
@ -385,12 +395,8 @@ class NetworkSession{
try{
$stream = new BinaryStream($decompressed);
$count = 0;
foreach(PacketBatch::decodeRaw($stream) as $buffer){
$this->gamePacketLimiter->decrement();
if(++$count > 100){
throw new PacketHandlingException("Too many packets in batch");
}
$packet = $this->packetPool->getPacket($buffer);
if($packet === null){
$this->logger->debug("Unknown packet: " . base64_encode($buffer));
@ -435,7 +441,7 @@ class NetworkSession{
$decodeTimings = Timings::getDecodeDataPacketTimings($packet);
$decodeTimings->startTiming();
try{
$stream = PacketSerializer::decoder($buffer, 0, $this->packetSerializerContext);
$stream = PacketSerializer::decoder($buffer, 0);
try{
$packet->decode($stream);
}catch(PacketDecodeException $e){
@ -470,7 +476,23 @@ class NetworkSession{
}
}
public function sendDataPacket(ClientboundPacket $packet, bool $immediate = false) : bool{
public function handleAckReceipt(int $receiptId) : void{
if(!$this->connected){
return;
}
if(isset($this->ackPromisesByReceiptId[$receiptId])){
$promises = $this->ackPromisesByReceiptId[$receiptId];
unset($this->ackPromisesByReceiptId[$receiptId]);
foreach($promises as $promise){
$promise->resolve(true);
}
}
}
/**
* @phpstan-param PromiseResolver<true>|null $ackReceiptResolver
*/
private function sendDataPacketInternal(ClientboundPacket $packet, bool $immediate, ?PromiseResolver $ackReceiptResolver) : bool{
if(!$this->connected){
return false;
}
@ -493,8 +515,11 @@ class NetworkSession{
$packets = [$packet];
}
if($ackReceiptResolver !== null){
$this->sendBufferAckPromises[] = $ackReceiptResolver;
}
foreach($packets as $evPacket){
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $evPacket));
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder(), $evPacket));
}
if($immediate){
$this->flushSendBuffer(true);
@ -506,6 +531,23 @@ class NetworkSession{
}
}
public function sendDataPacket(ClientboundPacket $packet, bool $immediate = false) : bool{
return $this->sendDataPacketInternal($packet, $immediate, null);
}
/**
* @phpstan-return Promise<true>
*/
public function sendDataPacketWithReceipt(ClientboundPacket $packet, bool $immediate = false) : Promise{
$resolver = new PromiseResolver();
if(!$this->sendDataPacketInternal($packet, $immediate, $resolver)){
$resolver->reject();
}
return $resolver->getPromise();
}
/**
* @internal
*/
@ -547,15 +589,15 @@ class NetworkSession{
$batch = $stream->getBuffer();
}
$this->sendBuffer = [];
$this->queueCompressedNoBufferFlush($batch, $immediate);
$ackPromises = $this->sendBufferAckPromises;
$this->sendBufferAckPromises = [];
$this->queueCompressedNoBufferFlush($batch, $immediate, $ackPromises);
}finally{
Timings::$playerNetworkSend->stopTiming();
}
}
}
public function getPacketSerializerContext() : PacketSerializerContext{ return $this->packetSerializerContext; }
public function getBroadcaster() : PacketBroadcaster{ return $this->broadcaster; }
public function getEntityEventBroadcaster() : EntityEventBroadcaster{ return $this->entityEventBroadcaster; }
@ -576,22 +618,27 @@ class NetworkSession{
}
}
private function queueCompressedNoBufferFlush(CompressBatchPromise|string $batch, bool $immediate = false) : void{
/**
* @param PromiseResolver[] $ackPromises
*
* @phpstan-param list<PromiseResolver<true>> $ackPromises
*/
private function queueCompressedNoBufferFlush(CompressBatchPromise|string $batch, bool $immediate = false, array $ackPromises = []) : void{
Timings::$playerNetworkSend->startTiming();
try{
if(is_string($batch)){
if($immediate){
//Skips all queues
$this->sendEncoded($batch, true);
$this->sendEncoded($batch, true, $ackPromises);
}else{
$this->compressedQueue->enqueue($batch);
$this->compressedQueue->enqueue([$batch, $ackPromises]);
$this->flushCompressedQueue();
}
}elseif($immediate){
//Skips all queues
$this->sendEncoded($batch->getResult(), true);
$this->sendEncoded($batch->getResult(), true, $ackPromises);
}else{
$this->compressedQueue->enqueue($batch);
$this->compressedQueue->enqueue([$batch, $ackPromises]);
$batch->onResolve(function() : void{
if($this->connected){
$this->flushCompressedQueue();
@ -608,14 +655,14 @@ class NetworkSession{
try{
while(!$this->compressedQueue->isEmpty()){
/** @var CompressBatchPromise|string $current */
$current = $this->compressedQueue->bottom();
[$current, $ackPromises] = $this->compressedQueue->bottom();
if(is_string($current)){
$this->compressedQueue->dequeue();
$this->sendEncoded($current);
$this->sendEncoded($current, false, $ackPromises);
}elseif($current->hasResult()){
$this->compressedQueue->dequeue();
$this->sendEncoded($current->getResult());
$this->sendEncoded($current->getResult(), false, $ackPromises);
}else{
//can't send any more queued until this one is ready
@ -627,13 +674,24 @@ class NetworkSession{
}
}
private function sendEncoded(string $payload, bool $immediate = false) : void{
/**
* @param PromiseResolver[] $ackPromises
* @phpstan-param list<PromiseResolver<true>> $ackPromises
*/
private function sendEncoded(string $payload, bool $immediate, array $ackPromises) : void{
if($this->cipher !== null){
Timings::$playerNetworkSendEncrypt->startTiming();
$payload = $this->cipher->encrypt($payload);
Timings::$playerNetworkSendEncrypt->stopTiming();
}
$this->sender->send($payload, $immediate);
if(count($ackPromises) > 0){
$ackReceiptId = $this->nextAckReceiptId++;
$this->ackPromisesByReceiptId[$ackReceiptId] = $ackPromises;
}else{
$ackReceiptId = null;
}
$this->sender->send($payload, $immediate, $ackReceiptId);
}
/**
@ -653,6 +711,19 @@ class NetworkSession{
$this->setHandler(null);
$this->connected = false;
$ackPromisesByReceiptId = $this->ackPromisesByReceiptId;
$this->ackPromisesByReceiptId = [];
foreach($ackPromisesByReceiptId as $resolvers){
foreach($resolvers as $resolver){
$resolver->reject();
}
}
$sendBufferAckPromises = $this->sendBufferAckPromises;
$this->sendBufferAckPromises = [];
foreach($sendBufferAckPromises as $resolver){
$resolver->reject();
}
$this->logger->info($this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_network_session_close($reason)));
}
}
@ -848,7 +919,19 @@ class NetworkSession{
$this->sendDataPacket(PlayStatusPacket::create(PlayStatusPacket::LOGIN_SUCCESS));
$this->logger->debug("Initiating resource packs phase");
$this->setHandler(new ResourcePacksPacketHandler($this, $this->server->getResourcePackManager(), function() : void{
$packManager = $this->server->getResourcePackManager();
$resourcePacks = $packManager->getResourceStack();
$keys = [];
foreach($resourcePacks as $resourcePack){
$key = $packManager->getPackEncryptionKey($resourcePack->getPackId());
if($key !== null){
$keys[$resourcePack->getPackId()] = $key;
}
}
$event = new PlayerResourcePackOfferEvent($this->info, $resourcePacks, $keys, $packManager->resourcePacksRequired());
$event->call();
$this->setHandler(new ResourcePacksPacketHandler($this, $event->getResourcePacks(), $event->getEncryptionKeys(), $event->mustAccept(), function() : void{
$this->createPlayer();
}));
}

View File

@ -28,7 +28,7 @@ interface PacketSender{
/**
* Pushes a packet into the channel to be processed.
*/
public function send(string $payload, bool $immediate) : void;
public function send(string $payload, bool $immediate, ?int $receiptId) : void;
/**
* Closes the channel, terminating the connection.

View File

@ -87,12 +87,13 @@ final class StandardEntityEventBroadcaster implements EntityEventBroadcaster{
EffectIdMap::getInstance()->toId($effect->getType()),
$effect->getAmplifier(),
$effect->isVisible(),
$effect->getDuration()
$effect->getDuration(),
tick: 0
));
}
public function onEntityEffectRemoved(array $recipients, Living $entity, EffectInstance $effect) : void{
$this->sendDataPacket($recipients, MobEffectPacket::remove($entity->getId(), EffectIdMap::getInstance()->toId($effect->getType())));
$this->sendDataPacket($recipients, MobEffectPacket::remove($entity->getId(), EffectIdMap::getInstance()->toId($effect->getType()), tick: 0));
}
public function onEntityRemoved(array $recipients, Entity $entity) : void{

View File

@ -26,7 +26,6 @@ namespace pocketmine\network\mcpe;
use pocketmine\event\server\DataPacketSendEvent;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\utils\BinaryStream;
@ -37,8 +36,7 @@ use function strlen;
final class StandardPacketBroadcaster implements PacketBroadcaster{
public function __construct(
private Server $server,
private PacketSerializerContext $protocolContext
private Server $server
){}
public function broadcastPackets(array $recipients, array $packets) : void{
@ -58,10 +56,6 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
/** @var NetworkSession[][] $targetsByCompressor */
$targetsByCompressor = [];
foreach($recipients as $recipient){
if($recipient->getPacketSerializerContext() !== $this->protocolContext){
throw new \InvalidArgumentException("Only recipients with the same protocol context as the broadcaster can be broadcast to by this broadcaster");
}
//TODO: different compressors might be compatible, it might not be necessary to split them up by object
$compressor = $recipient->getCompressor();
$compressors[spl_object_id($compressor)] = $compressor;
@ -72,7 +66,7 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
$totalLength = 0;
$packetBuffers = [];
foreach($packets as $packet){
$buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder($this->protocolContext), $packet);
$buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder(), $packet);
//varint length prefix + packet buffer
$totalLength += (((int) log(strlen($buffer), 128)) + 1) + strlen($buffer);
$packetBuffers[] = $buffer;

View File

@ -39,16 +39,6 @@ use function time;
class ProcessLoginTask extends AsyncTask{
private const TLS_KEY_ON_COMPLETION = "completion";
/**
* Old Mojang root auth key. This was used since the introduction of Xbox Live authentication in 0.15.0.
* This key is expected to be replaced by the key below in the future, but this has not yet happened as of
* 2023-07-01.
* Ideally we would place a time expiry on this key, but since Mojang have not given a hard date for the key change,
* and one bad guess has already caused a major outage, we can't do this.
* TODO: This needs to be removed as soon as the new key is deployed by Mojang's authentication servers.
*/
public const MOJANG_OLD_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
/**
* New Mojang root auth key. Mojang notified third-party developers of this change prior to the release of 1.20.0.
* Expectations were that this would be used starting a "couple of weeks" after the release, but as of 2023-07-01,
@ -128,7 +118,6 @@ class ProcessLoginTask extends AsyncTask{
try{
[$headersArray, $claimsArray, ] = JwtUtils::parse($jwt);
}catch(JwtException $e){
//TODO: we shouldn't be showing internal information like this to the client
throw new VerifyLoginException("Failed to parse JWT: " . $e->getMessage(), null, 0, $e);
}
@ -142,13 +131,11 @@ class ProcessLoginTask extends AsyncTask{
/** @var JwtHeader $headers */
$headers = $mapper->map($headersArray, new JwtHeader());
}catch(\JsonMapper_Exception $e){
//TODO: we shouldn't be showing internal information like this to the client
throw new VerifyLoginException("Invalid JWT header: " . $e->getMessage(), null, 0, $e);
}
$headerDerKey = base64_decode($headers->x5u, true);
if($headerDerKey === false){
//TODO: we shouldn't be showing internal information like this to the client
throw new VerifyLoginException("Invalid JWT public key: base64 decoding error decoding x5u");
}
@ -164,7 +151,6 @@ class ProcessLoginTask extends AsyncTask{
try{
$signingKeyOpenSSL = JwtUtils::parseDerPublicKey($headerDerKey);
}catch(JwtException $e){
//TODO: we shouldn't be showing this internal information to the client
throw new VerifyLoginException("Invalid JWT public key: " . $e->getMessage(), null, 0, $e);
}
try{
@ -175,7 +161,7 @@ class ProcessLoginTask extends AsyncTask{
throw new VerifyLoginException($e->getMessage(), null, 0, $e);
}
if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY || $headers->x5u === self::MOJANG_OLD_ROOT_PUBLIC_KEY){
if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY){
$this->authenticated = true; //we're signed into xbox live
}

View File

@ -113,7 +113,8 @@ final class CraftingDataCache{
$nullUUID,
CraftingRecipeBlockName::CRAFTING_TABLE,
50,
$index
true,
$index,
);
}else{
//TODO: probably special recipe types

View File

@ -38,7 +38,7 @@ final class ZlibCompressor implements Compressor{
public const DEFAULT_LEVEL = 7;
public const DEFAULT_THRESHOLD = 256;
public const DEFAULT_MAX_DECOMPRESSION_SIZE = 2 * 1024 * 1024;
public const DEFAULT_MAX_DECOMPRESSION_SIZE = 8 * 1024 * 1024;
/**
* @see SingletonTrait::make()

View File

@ -30,13 +30,17 @@ use pocketmine\crafting\RecipeIngredient;
use pocketmine\crafting\TagWildcardRecipeIngredient;
use pocketmine\data\bedrock\BedrockDataFiles;
use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\data\bedrock\item\ItemTypeNames;
use pocketmine\item\Item;
use pocketmine\item\VanillaItems;
use pocketmine\nbt\NbtException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\types\GameMode as ProtocolGameMode;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStack;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraData;
use pocketmine\network\mcpe\protocol\types\inventory\ItemStackExtraDataShield;
use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor;
use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient as ProtocolRecipeIngredient;
use pocketmine\network\mcpe\protocol\types\recipe\StringIdMetaItemDescriptor;
@ -76,7 +80,7 @@ class TypeConverter{
);
$this->itemTypeDictionary = ItemTypeDictionaryFromDataHelper::loadFromString(Filesystem::fileGetContents(BedrockDataFiles::REQUIRED_ITEM_LIST_JSON));
$this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId("minecraft:shield");
$this->shieldRuntimeId = $this->itemTypeDictionary->fromStringId(ItemTypeNames::SHIELD);
$this->itemTranslator = new ItemTranslator(
$this->itemTypeDictionary,
@ -217,26 +221,36 @@ class TypeConverter{
[$id, $meta, $blockRuntimeId] = $idMeta;
}
$extraData = $id === $this->shieldRuntimeId ?
new ItemStackExtraDataShield($nbt, canPlaceOn: [], canDestroy: [], blockingTick: 0) :
new ItemStackExtraData($nbt, canPlaceOn: [], canDestroy: []);
$extraDataSerializer = PacketSerializer::encoder();
$extraData->write($extraDataSerializer);
return new ItemStack(
$id,
$meta,
$itemStack->getCount(),
$blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
$nbt,
[],
[],
$id === $this->shieldRuntimeId ? 0 : null
$extraDataSerializer->getBuffer(),
);
}
/**
* WARNING: Avoid this in server-side code. If you need to compare ItemStacks provided by the client to the
* server, prefer encoding the server's itemstack and comparing the serialized ItemStack, instead of converting
* the client's ItemStack to a core Item.
* This method will fully decode the item's extra data, which can be very costly if the item has a lot of NBT data.
*
* @throws TypeConversionException
*/
public function netItemStackToCore(ItemStack $itemStack) : Item{
if($itemStack->getId() === 0){
return VanillaItems::AIR();
}
$compound = $itemStack->getNbt();
$extraData = $this->deserializeItemStackExtraData($itemStack->getRawExtraData(), $itemStack->getId());
$compound = $extraData->getNbt();
$itemResult = $this->itemTranslator->fromNetworkId($itemStack->getId(), $itemStack->getMeta(), $itemStack->getBlockRuntimeId());
@ -255,4 +269,11 @@ class TypeConverter{
return $itemResult;
}
public function deserializeItemStackExtraData(string $extraData, int $id) : ItemStackExtraData{
$extraDataDeserializer = PacketSerializer::decoder($extraData, 0);
return $id === $this->shieldRuntimeId ?
ItemStackExtraDataShield::read($extraDataDeserializer) :
ItemStackExtraData::read($extraDataDeserializer);
}
}

View File

@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\handler;
use pocketmine\block\BaseSign;
use pocketmine\block\ItemFrame;
use pocketmine\block\Lectern;
use pocketmine\block\tile\Sign;
use pocketmine\block\utils\SignText;
@ -60,7 +59,6 @@ use pocketmine\network\mcpe\protocol\ContainerClosePacket;
use pocketmine\network\mcpe\protocol\EmotePacket;
use pocketmine\network\mcpe\protocol\InteractPacket;
use pocketmine\network\mcpe\protocol\InventoryTransactionPacket;
use pocketmine\network\mcpe\protocol\ItemFrameDropItemPacket;
use pocketmine\network\mcpe\protocol\ItemStackRequestPacket;
use pocketmine\network\mcpe\protocol\ItemStackResponsePacket;
use pocketmine\network\mcpe\protocol\LabTablePacket;
@ -444,9 +442,18 @@ class InGamePacketHandler extends PacketHandler{
return false;
}
$serverItemStack = $this->session->getTypeConverter()->coreItemStackToNet($sourceSlotItem);
//because the client doesn't tell us the expected itemstack ID, we have to deep-compare our known
//itemstack info with the one the client sent. This is costly, but we don't have any other option :(
if(!$serverItemStack->equals($clientItemStack)){
//Sadly we don't have itemstack IDs here, so we have to compare the basic item properties to ensure that we're
//dropping the item the client expects (inventory might be out of sync with the client).
if(
$serverItemStack->getId() !== $clientItemStack->getId() ||
$serverItemStack->getMeta() !== $clientItemStack->getMeta() ||
$serverItemStack->getCount() !== $clientItemStack->getCount() ||
$serverItemStack->getBlockRuntimeId() !== $clientItemStack->getBlockRuntimeId()
//Raw extraData may not match because of TAG_Compound key ordering differences, and decoding it to compare
//is costly. Assume that we're in sync if id+meta+count+runtimeId match.
//NB: Make sure $clientItemStack isn't used to create the dropped item, as that would allow the client
//to change the item NBT since we're not validating it.
){
return false;
}
@ -799,15 +806,6 @@ class InGamePacketHandler extends PacketHandler{
return true;
}
public function handleItemFrameDropItem(ItemFrameDropItemPacket $packet) : bool{
$blockPosition = $packet->blockPosition;
$block = $this->player->getWorld()->getBlockAt($blockPosition->getX(), $blockPosition->getY(), $blockPosition->getZ());
if($block instanceof ItemFrame && $block->getFramedItem() !== null){
return $this->player->attackBlock(new Vector3($blockPosition->getX(), $blockPosition->getY(), $blockPosition->getZ()), $block->getFacing());
}
return false;
}
public function handleBossEvent(BossEventPacket $packet) : bool{
return false; //TODO
}
@ -989,11 +987,6 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleLecternUpdate(LecternUpdatePacket $packet) : bool{
if($packet->dropBook){
//Drop book is handled with an interact event on use item transaction
return true;
}
$pos = $packet->blockPosition;
$chunkX = $pos->getX() >> Chunk::COORD_BIT_SIZE;
$chunkZ = $pos->getZ() >> Chunk::COORD_BIT_SIZE;

View File

@ -37,12 +37,13 @@ use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType;
use pocketmine\resourcepacks\ResourcePack;
use pocketmine\resourcepacks\ResourcePackManager;
use function array_keys;
use function array_map;
use function ceil;
use function count;
use function implode;
use function strpos;
use function strtolower;
use function substr;
/**
@ -50,37 +51,77 @@ use function substr;
* packs to the client.
*/
class ResourcePacksPacketHandler extends PacketHandler{
private const PACK_CHUNK_SIZE = 128 * 1024; //128KB
private const PACK_CHUNK_SIZE = 256 * 1024; //256KB
/**
* Larger values allow downloading more chunks at the same time, increasing download speed, but the client may choke
* and cause the download speed to drop (due to ACKs taking too long to arrive).
*/
private const MAX_CONCURRENT_CHUNK_REQUESTS = 1;
/**
* @var ResourcePack[]
* @phpstan-var array<string, ResourcePack>
*/
private array $resourcePacksById = [];
/** @var bool[][] uuid => [chunk index => hasSent] */
private array $downloadedChunks = [];
/** @phpstan-var \SplQueue<array{ResourcePack, int}> */
private \SplQueue $requestQueue;
private int $activeRequests = 0;
/**
* @phpstan-param \Closure() : void $completionCallback
* @param ResourcePack[] $resourcePackStack
* @param string[] $encryptionKeys pack UUID => key, leave unset for any packs that are not encrypted
*
* @phpstan-param list<ResourcePack> $resourcePackStack
* @phpstan-param array<string, string> $encryptionKeys
* @phpstan-param \Closure() : void $completionCallback
*/
public function __construct(
private NetworkSession $session,
private ResourcePackManager $resourcePackManager,
private array $resourcePackStack,
private array $encryptionKeys,
private bool $mustAccept,
private \Closure $completionCallback
){}
){
$this->requestQueue = new \SplQueue();
foreach($resourcePackStack as $pack){
$this->resourcePacksById[$pack->getPackId()] = $pack;
}
}
private function getPackById(string $id) : ?ResourcePack{
return $this->resourcePacksById[strtolower($id)] ?? null;
}
public function setUp() : void{
$resourcePackEntries = array_map(function(ResourcePack $pack) : ResourcePackInfoEntry{
//TODO: more stuff
$encryptionKey = $this->resourcePackManager->getPackEncryptionKey($pack->getPackId());
return new ResourcePackInfoEntry(
$pack->getPackId(),
$pack->getPackVersion(),
$pack->getPackSize(),
$encryptionKey ?? "",
$this->encryptionKeys[$pack->getPackId()] ?? "",
"",
$pack->getPackId(),
false
);
}, $this->resourcePackManager->getResourceStack());
}, $this->resourcePackStack);
//TODO: support forcing server packs
$this->session->sendDataPacket(ResourcePacksInfoPacket::create($resourcePackEntries, [], $this->resourcePackManager->resourcePacksRequired(), false, false, []));
$this->session->sendDataPacket(ResourcePacksInfoPacket::create(
resourcePackEntries: $resourcePackEntries,
behaviorPackEntries: [],
mustAccept: $this->mustAccept,
hasAddons: false,
hasScripts: false,
forceServerPacks: false,
cdnUrls: []
));
$this->session->getLogger()->debug("Waiting for client to accept resource packs");
}
@ -104,11 +145,11 @@ class ResourcePacksPacketHandler extends PacketHandler{
if($splitPos !== false){
$uuid = substr($uuid, 0, $splitPos);
}
$pack = $this->resourcePackManager->getPackById($uuid);
$pack = $this->getPackById($uuid);
if(!($pack instanceof ResourcePack)){
//Client requested a resource pack but we don't have it available on the server
$this->disconnectWithError("Unknown pack $uuid requested, available packs: " . implode(", ", $this->resourcePackManager->getPackIdList()));
$this->disconnectWithError("Unknown pack $uuid requested, available packs: " . implode(", ", array_keys($this->resourcePacksById)));
return false;
}
@ -128,7 +169,7 @@ class ResourcePacksPacketHandler extends PacketHandler{
case ResourcePackClientResponsePacket::STATUS_HAVE_ALL_PACKS:
$stack = array_map(static function(ResourcePack $pack) : ResourcePackStackEntry{
return new ResourcePackStackEntry($pack->getPackId(), $pack->getPackVersion(), ""); //TODO: subpacks
}, $this->resourcePackManager->getResourceStack());
}, $this->resourcePackStack);
//we support chemistry blocks by default, the client should already have this installed
$stack[] = new ResourcePackStackEntry("0fba4063-dba1-4281-9b89-ff9390653530", "1.0.0", "");
@ -136,7 +177,7 @@ class ResourcePacksPacketHandler extends PacketHandler{
//we don't force here, because it doesn't have user-facing effects
//but it does have an annoying side-effect when true: it makes
//the client remove its own non-server-supplied resource packs.
$this->session->sendDataPacket(ResourcePackStackPacket::create($stack, [], false, ProtocolInfo::MINECRAFT_VERSION_NETWORK, new Experiments([], false)));
$this->session->sendDataPacket(ResourcePackStackPacket::create($stack, [], false, ProtocolInfo::MINECRAFT_VERSION_NETWORK, new Experiments([], false), false));
$this->session->getLogger()->debug("Applying resource pack stack");
break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED:
@ -151,9 +192,9 @@ class ResourcePacksPacketHandler extends PacketHandler{
}
public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{
$pack = $this->resourcePackManager->getPackById($packet->packId);
$pack = $this->getPackById($packet->packId);
if(!($pack instanceof ResourcePack)){
$this->disconnectWithError("Invalid request for chunk $packet->chunkIndex of unknown pack $packet->packId, available packs: " . implode(", ", $this->resourcePackManager->getPackIdList()));
$this->disconnectWithError("Invalid request for chunk $packet->chunkIndex of unknown pack $packet->packId, available packs: " . implode(", ", array_keys($this->resourcePacksById)));
return false;
}
@ -176,8 +217,37 @@ class ResourcePacksPacketHandler extends PacketHandler{
$this->downloadedChunks[$packId][$packet->chunkIndex] = true;
}
$this->session->sendDataPacket(ResourcePackChunkDataPacket::create($packId, $packet->chunkIndex, $offset, $pack->getPackChunk($offset, self::PACK_CHUNK_SIZE)));
$this->requestQueue->enqueue([$pack, $packet->chunkIndex]);
$this->processChunkRequestQueue();
return true;
}
private function processChunkRequestQueue() : void{
if($this->activeRequests >= self::MAX_CONCURRENT_CHUNK_REQUESTS || $this->requestQueue->isEmpty()){
return;
}
/**
* @var ResourcePack $pack
* @var int $chunkIndex
*/
[$pack, $chunkIndex] = $this->requestQueue->dequeue();
$packId = $pack->getPackId();
$offset = $chunkIndex * self::PACK_CHUNK_SIZE;
$chunkData = $pack->getPackChunk($offset, self::PACK_CHUNK_SIZE);
$this->activeRequests++;
$this->session
->sendDataPacketWithReceipt(ResourcePackChunkDataPacket::create($packId, $chunkIndex, $offset, $chunkData))
->onCompletion(
function() : void{
$this->activeRequests--;
$this->processChunkRequestQueue();
},
function() : void{
//this may have been rejected because of a disconnection - this will do nothing in that case
$this->disconnectWithError("Plugin interrupted sending of resource packs");
}
);
}
}

View File

@ -33,7 +33,6 @@ use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\PacketBroadcaster;
use pocketmine\network\mcpe\protocol\PacketPool;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\Network;
use pocketmine\network\NetworkInterfaceStartException;
use pocketmine\network\PacketHandlingException;
@ -84,7 +83,6 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
private PacketBroadcaster $packetBroadcaster;
private EntityEventBroadcaster $entityEventBroadcaster;
private PacketSerializerContext $packetSerializerContext;
private TypeConverter $typeConverter;
public function __construct(
@ -94,12 +92,10 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
bool $ipV6,
PacketBroadcaster $packetBroadcaster,
EntityEventBroadcaster $entityEventBroadcaster,
PacketSerializerContext $packetSerializerContext,
TypeConverter $typeConverter
){
$this->server = $server;
$this->packetBroadcaster = $packetBroadcaster;
$this->packetSerializerContext = $packetSerializerContext;
$this->entityEventBroadcaster = $entityEventBroadcaster;
$this->typeConverter = $typeConverter;
@ -192,7 +188,6 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
$this->server,
$this->network->getSessionManager(),
PacketPool::getInstance(),
$this->packetSerializerContext,
new RakLibPacketSender($sessionId, $this),
$this->packetBroadcaster,
$this->entityEventBroadcaster,
@ -257,7 +252,9 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
}
public function onPacketAck(int $sessionId, int $identifierACK) : void{
if(isset($this->sessions[$sessionId])){
$this->sessions[$sessionId]->handleAckReceipt($identifierACK);
}
}
public function setName(string $name) : void{
@ -294,12 +291,13 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
$this->network->getBandwidthTracker()->add($bytesSentDiff, $bytesReceivedDiff);
}
public function putPacket(int $sessionId, string $payload, bool $immediate = true) : void{
public function putPacket(int $sessionId, string $payload, bool $immediate = true, ?int $receiptId = null) : void{
if(isset($this->sessions[$sessionId])){
$pk = new EncapsulatedPacket();
$pk->buffer = self::MCPE_RAKNET_PACKET_ID . $payload;
$pk->reliability = PacketReliability::RELIABLE_ORDERED;
$pk->orderChannel = 0;
$pk->identifierACK = $receiptId;
$this->interface->sendEncapsulated($sessionId, $pk, $immediate);
}

View File

@ -33,9 +33,9 @@ class RakLibPacketSender implements PacketSender{
private RakLibInterface $handler
){}
public function send(string $payload, bool $immediate) : void{
public function send(string $payload, bool $immediate, ?int $receiptId) : void{
if(!$this->closed){
$this->handler->putPacket($this->sessionId, $payload, $immediate);
$this->handler->putPacket($this->sessionId, $payload, $immediate, $receiptId);
}
}

View File

@ -96,7 +96,8 @@ class RakLibServer extends Thread{
new SimpleProtocolAcceptor($this->protocolVersion),
new UserToRakLibThreadMessageReceiver(new PthreadsChannelReader($this->mainToThreadBuffer)),
new RakLibToUserThreadMessageSender(new SnoozeAwarePthreadsChannelWriter($this->threadToMainBuffer, $this->sleeperEntry->createNotifier())),
new ExceptionTraceCleaner($this->mainPath)
new ExceptionTraceCleaner($this->mainPath),
recvMaxSplitParts: 512
);
$this->synchronized(function() : void{
$this->ready = true;

View File

@ -30,7 +30,6 @@ use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\convert\BlockTranslator;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\utils\Binary;
use pocketmine\utils\BinaryStream;
@ -84,8 +83,8 @@ final class ChunkSerializer{
/**
* @phpstan-param DimensionIds::* $dimensionId
*/
public static function serializeFullChunk(Chunk $chunk, int $dimensionId, BlockTranslator $blockTranslator, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{
$stream = PacketSerializer::encoder($encoderContext);
public static function serializeFullChunk(Chunk $chunk, int $dimensionId, BlockTranslator $blockTranslator, ?string $tiles = null) : string{
$stream = PacketSerializer::encoder();
$subChunkCount = self::getSubChunkCount($chunk, $dimensionId);
$writtenCount = 0;

View File

@ -630,6 +630,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->displayName = $ev->getNewName();
}
public function canBeRenamed() : bool{
return false;
}
/**
* Returns the player's locale, e.g. en_US.
*/
@ -1389,7 +1393,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
public function setMotion(Vector3 $motion) : bool{
if(parent::setMotion($motion)){
$this->broadcastMotion();
$this->getNetworkSession()->sendDataPacket(SetActorMotionPacket::create($this->id, $motion));
$this->getNetworkSession()->sendDataPacket(SetActorMotionPacket::create($this->id, $motion, tick: 0));
return true;
}

View File

@ -650,6 +650,11 @@ class PluginManager{
$handlerName = Utils::getNiceClosureName($handler);
$reflect = new \ReflectionFunction($handler);
if($reflect->isGenerator()){
throw new PluginException("Generator function $handlerName cannot be used as an event handler");
}
if(!$plugin->isEnabled()){
throw new PluginException("Plugin attempted to register event handler " . $handlerName . "() to event " . $event . " while not enabled");
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\promise;
use function count;
use function spl_object_id;
/**
@ -57,4 +58,53 @@ final class Promise{
//rejected or just hasn't been resolved yet
return $this->shared->state === true;
}
/**
* Returns a promise that will resolve only once all the Promises in
* `$promises` have resolved. The resolution value of the returned promise
* will be an array containing the resolution values of each Promises in
* `$promises` indexed by the respective Promises' array keys.
*
* @param Promise[] $promises
*
* @phpstan-template TPromiseValue
* @phpstan-template TKey of array-key
* @phpstan-param non-empty-array<TKey, Promise<TPromiseValue>> $promises
*
* @phpstan-return Promise<array<TKey, TPromiseValue>>
*/
public static function all(array $promises) : Promise{
if(count($promises) === 0){
throw new \InvalidArgumentException("At least one promise must be provided");
}
/** @phpstan-var PromiseResolver<array<TKey, TPromiseValue>> $resolver */
$resolver = new PromiseResolver();
$values = [];
$toResolve = count($promises);
$continue = true;
foreach($promises as $key => $promise){
$promise->onCompletion(
function(mixed $value) use ($resolver, $key, $toResolve, &$values) : void{
$values[$key] = $value;
if(count($values) === $toResolve){
$resolver->resolve($values);
}
},
function() use ($resolver, &$continue) : void{
if($continue){
$continue = false;
$resolver->reject();
}
}
);
if(!$continue){
break;
}
}
return $resolver->getPromise();
}
}

View File

@ -24,12 +24,10 @@ declare(strict_types=1);
namespace pocketmine\scheduler;
use pmmp\thread\Runnable;
use pmmp\thread\Thread as NativeThread;
use pmmp\thread\ThreadSafe;
use pmmp\thread\ThreadSafeArray;
use pocketmine\thread\NonThreadSafeValue;
use function array_key_exists;
use function assert;
use function igbinary_serialize;
use function igbinary_unserialize;
use function is_null;
@ -83,9 +81,7 @@ abstract class AsyncTask extends Runnable{
$this->onRun();
$this->finished = true;
$worker = NativeThread::getCurrentThread();
assert($worker instanceof AsyncWorker);
$worker->getNotifier()->wakeupSleeper();
AsyncWorker::getNotifier()->wakeupSleeper();
}
/**

View File

@ -36,7 +36,7 @@ class AsyncWorker extends Worker{
/** @var mixed[] */
private static array $store = [];
private const TLS_KEY_NOTIFIER = self::class . "::notifier";
private static ?SleeperNotifier $notifier = null;
public function __construct(
private ThreadSafeLogger $logger,
@ -45,12 +45,11 @@ class AsyncWorker extends Worker{
private SleeperHandlerEntry $sleeperEntry
){}
public function getNotifier() : SleeperNotifier{
$notifier = $this->getFromThreadStore(self::TLS_KEY_NOTIFIER);
if(!$notifier instanceof SleeperNotifier){
throw new AssumptionFailedError("SleeperNotifier not found in thread-local storage");
public static function getNotifier() : SleeperNotifier{
if(self::$notifier !== null){
return self::$notifier;
}
return $notifier;
throw new AssumptionFailedError("SleeperNotifier not found in thread-local storage");
}
protected function onRun() : void{
@ -66,7 +65,7 @@ class AsyncWorker extends Worker{
$this->logger->debug("No memory limit set");
}
$this->saveToThreadStore(self::TLS_KEY_NOTIFIER, $this->sleeperEntry->createNotifier());
self::$notifier = $this->sleeperEntry->createNotifier();
}
public function getLogger() : ThreadSafeLogger{
@ -84,6 +83,8 @@ class AsyncWorker extends Worker{
/**
* Saves mixed data into the worker's thread-local object store. This can be used to store objects which you
* want to use on this worker thread from multiple AsyncTasks.
*
* @deprecated Use static class properties instead.
*/
public function saveToThreadStore(string $identifier, mixed $value) : void{
if(NativeThread::getCurrentThread() !== $this){
@ -99,6 +100,8 @@ class AsyncWorker extends Worker{
* account for the possibility that what you're trying to retrieve might not exist.
*
* Objects stored in this storage may ONLY be retrieved while the task is running.
*
* @deprecated Use static class properties instead.
*/
public function getFromThreadStore(string $identifier) : mixed{
if(NativeThread::getCurrentThread() !== $this){
@ -109,6 +112,8 @@ class AsyncWorker extends Worker{
/**
* Removes previously-stored mixed data from the worker's thread-local object store.
*
* @deprecated Use static class properties instead.
*/
public function removeFromThreadStore(string $identifier) : void{
if(NativeThread::getCurrentThread() !== $this){

View File

@ -23,7 +23,9 @@ declare(strict_types=1);
namespace pocketmine\thread;
use pmmp\thread\Thread as NativeThread;
use pmmp\thread\ThreadSafeArray;
use pocketmine\crash\CrashDump;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\Server;
use function error_get_last;
@ -77,9 +79,7 @@ trait CommonThreadPartsTrait{
/**
* Registers the class loaders for this thread.
*
* WARNING: This method MUST be called from any descendent threads' run() method to make autoloading usable.
* If you do not do this, you will not be able to use new classes that were not loaded when the thread was started
* (unless you are using a custom autoloader).
* @internal
*/
public function registerClassLoaders() : void{
if($this->composerAutoloaderPath !== null){
@ -96,6 +96,15 @@ trait CommonThreadPartsTrait{
public function getCrashInfo() : ?ThreadCrashInfo{ return $this->crashInfo; }
public function start(int $options = NativeThread::INHERIT_NONE) : bool{
ThreadManager::getInstance()->add($this);
if($this->getClassLoaders() === null){
$this->setClassLoaders();
}
return parent::start($options);
}
final public function run() : void{
error_reporting(-1);
$this->registerClassLoaders();
@ -110,6 +119,20 @@ trait CommonThreadPartsTrait{
$this->isKilled = true;
}
/**
* Stops the thread using the best way possible. Try to stop it yourself before calling this.
*/
public function quit() : void{
$this->isKilled = true;
if(!$this->isJoined()){
$this->notify();
$this->join();
}
ThreadManager::getInstance()->remove($this);
}
/**
* Called by set_exception_handler() when an uncaught exception is thrown.
*/
@ -128,7 +151,7 @@ trait CommonThreadPartsTrait{
$this->synchronized(function() : void{
if($this->isTerminated() && $this->crashInfo === null){
$last = error_get_last();
if($last !== null){
if($last !== null && ($last["type"] & CrashDump::FATAL_ERROR_MASK) !== 0){
//fatal error
$crashInfo = ThreadCrashInfo::fromLastErrorInfo($last, $this->getThreadName());
}else{

View File

@ -37,28 +37,4 @@ use pocketmine\scheduler\AsyncTask;
*/
abstract class Thread extends NativeThread{
use CommonThreadPartsTrait;
public function start(int $options = NativeThread::INHERIT_NONE) : bool{
//this is intentionally not traitified
ThreadManager::getInstance()->add($this);
if($this->getClassLoaders() === null){
$this->setClassLoaders();
}
return parent::start($options);
}
/**
* Stops the thread using the best way possible. Try to stop it yourself before calling this.
*/
public function quit() : void{
$this->isKilled = true;
if(!$this->isJoined()){
$this->notify();
$this->join();
}
ThreadManager::getInstance()->remove($this);
}
}

View File

@ -51,12 +51,12 @@ class ThreadSafeClassLoader extends ThreadSafe{
* @var ThreadSafeArray|string[]
* @phpstan-var ThreadSafeArray<int, string>
*/
private $fallbackLookup;
private ThreadSafeArray $fallbackLookup;
/**
* @var ThreadSafeArray|string[][]
* @phpstan-var ThreadSafeArray<string, ThreadSafeArray<int, string>>
*/
private $psr4Lookup;
private ThreadSafeArray $psr4Lookup;
public function __construct(){
$this->fallbackLookup = new ThreadSafeArray();

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\thread;
use pmmp\thread\Thread as NativeThread;
use pmmp\thread\Worker as NativeWorker;
use pocketmine\scheduler\AsyncTask;
@ -39,31 +38,4 @@ use pocketmine\scheduler\AsyncTask;
*/
abstract class Worker extends NativeWorker{
use CommonThreadPartsTrait;
public function start(int $options = NativeThread::INHERIT_NONE) : bool{
//this is intentionally not traitified
ThreadManager::getInstance()->add($this);
if($this->getClassLoaders() === null){
$this->setClassLoaders();
}
return parent::start($options);
}
/**
* Stops the thread using the best way possible. Try to stop it yourself before calling this.
*/
public function quit() : void{
$this->isKilled = true;
if(!$this->isShutdown()){
$this->synchronized(function() : void{
while($this->unstack() !== null);
});
$this->notify();
$this->shutdown();
}
ThreadManager::getInstance()->remove($this);
}
}

View File

@ -210,8 +210,7 @@ abstract class Timings{
}
/**
* @phpstan-template T of object
* @phpstan-param class-string<T> $class
* @phpstan-param class-string<covariant object> $class
*/
private static function shortenCoreClassName(string $class, string $prefix) : string{
if(str_starts_with($class, $prefix)){
@ -302,8 +301,7 @@ abstract class Timings{
}
/**
* @phpstan-template TEvent of Event
* @phpstan-param class-string<TEvent> $event
* @phpstan-param class-string<covariant Event> $event
*/
public static function getEventHandlerTimings(string $event, string $handlerName, string $group) : TimingsHandler{
if(!isset(self::$eventHandlers[$event][$handlerName])){

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