Compare commits

..

30 Commits

Author SHA1 Message Date
173b685b02 Bedrock 1.21.100 (#6760)
---------

Co-authored-by: Dylan T. <dktapps@pmmp.io>
2025-08-06 16:41:44 +01:00
89d18f929f RakLibServer: fixed deadlock on thread crash
synchronized block -> getCrashInfo -> join -> synchronized on the same
context on the child thread -> deadlock

instead we check for isTerminated and then get the crash info outside
of the synchronized block.
2025-08-06 15:49:52 +01:00
a83c62a4a2 readme: Stop showing random PR statuses on CI badge 2025-07-30 19:08:29 +01:00
40ea6dd30d Bump phpstan/phpstan-strict-rules in the development-patch-updates group (#6758) 2025-07-29 09:26:25 +00:00
866df55edf Bump ramsey/uuid from 4.8.1 to 4.9.0 (#6748) 2025-07-29 09:25:19 +00:00
4047cbaafe Bump phpstan/phpstan-strict-rules in the development-patch-updates group (#6756) 2025-07-23 12:07:07 +00:00
a1d74b5710 5.31.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/16143550499
2025-07-08 12:43:11 +00:00
50e15db9ac Prepare 5.31.0 release (#6752) 2025-07-08 13:41:59 +01:00
f1f6e796a4 Bump the github-actions group with 2 updates (#6749) 2025-07-01 10:28:28 +00:00
7ea0f2ff43 copilot-setup-steps: also add extension stubs 2025-06-26 00:14:34 +01:00
6dbd4282cb fix cache key 2025-06-26 00:12:12 +01:00
3176e7549e woops 2025-06-26 00:11:26 +01:00
92c3ce7f02 Create copilot-setup-steps.yml 2025-06-26 00:10:46 +01:00
3a5432b316 Bump build/php from 1549433 to ce1b095 (#6741) 2025-06-25 00:16:12 +00:00
40a3ee68dd fix typo in changelog (#6745) 2025-06-24 13:35:32 +01:00
e415518435 5.30.2 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/15834488047
2025-06-23 20:31:58 +00:00
cb508f4382 Release 5.30.1 (#6744) 2025-06-23 23:30:48 +03:00
177fa76434 Disable client-side locator bar (#6743)
Without a propper server-side implementation, it just a mess of white dots of nearby players
2025-06-23 14:57:33 -05:00
2a97b4294d Fixed held block placement after respawn anchor explosion (#6742) 2025-06-23 16:59:40 +01:00
04494e845c EntityExplodeEvent: Fixed accidental BC break introduced by #6646
thanks @Yexeed
2025-06-20 15:26:42 +01:00
0e511ff783 smh 2025-06-18 21:55:53 +01:00
6826420876 5.30.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/15743323722
2025-06-18 20:49:31 +00:00
f0161c84b9 Merge pull request #6737 from pmmp/r5.30.0
5.30.0
2025-06-18 21:48:30 +01:00
3643d3aeb8 Ready 5.30.0 release 2025-06-18 20:42:10 +01:00
a662510cca Merge remote-tracking branch 'origin/stable' into minor-next 2025-06-18 19:56:02 +01:00
670d3fb997 Mention developer team in draft release notification 2025-06-18 19:29:28 +01:00
8843b1b568 5.29.1 is next
Commit created by: https://github.com/pmmp/RestrictedActions/actions/runs/15720776704
2025-06-18 00:16:13 +00:00
9c71f4fc1c Assemble 1.21.90 (#6736) 2025-06-18 01:15:00 +01:00
bd39caccb0 Merge 'stable' into 'minor-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/15598290427
2025-06-12 00:02:56 +00:00
95b4db5169 Fix slow SubChunk garbage collection check, closes #6574 (#6731) 2025-06-11 21:29:03 +01:00
42 changed files with 372 additions and 144 deletions

View File

@ -0,0 +1,47 @@
name: "Copilot Agent environment setup"
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml
jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: pmmp/setup-php-action@3.2.0
with:
php-version: 8.3
install-path: "./bin"
pm-version-major: 5
- name: Restore Composer package cache
uses: actions/cache@v4
with:
path: |
~/.cache/composer/files
~/.cache/composer/vcs
key: "composer-v2-cache-8.3-${{ hashFiles('./composer.lock') }}"
restore-keys: |
composer-v2-cache-
- name: Install Composer dependencies
run: composer install --prefer-dist --no-interaction
- name: Clone extension stubs
uses: actions/checkout@v4
with:
repository: pmmp/phpstorm-stubs
path: extension-stubs

View File

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

View File

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

View File

@ -87,7 +87,7 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.33.0
uses: shivammathur/setup-php@2.34.1
with:
php-version: ${{ env.PHP_VERSION }}
@ -165,7 +165,7 @@ jobs:
${{ github.workspace }}/core-permissions.rst
- name: Create draft release
uses: ncipollo/release-action@v1.16.0
uses: ncipollo/release-action@v1.18.0
id: create-draft
with:
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst
@ -188,4 +188,4 @@ jobs:
if: github.event_name == 'pull_request_target'
uses: thollander/actions-comment-pull-request@v3
with:
message: "[Draft release ${{ steps.get-pm-version.outputs.PM_VERSION }}](${{ steps.create-draft.outputs.html_url }}) has been created for commit ${{ github.sha }}. Please review and publish it."
message: "${{ vars.DRAFT_RELEASE_NOTIFICATION_MENTION }} [Draft release ${{ steps.get-pm-version.outputs.PM_VERSION }}](${{ steps.create-draft.outputs.html_url }}) has been created for commit ${{ github.sha }}. Please review and publish it."

View File

@ -28,7 +28,7 @@ jobs:
- uses: actions/checkout@v4
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.33.0
uses: shivammathur/setup-php@2.34.1
with:
php-version: 8.3
tools: php-cs-fixer:3.75

View File

@ -12,7 +12,7 @@
</p>
<p align="center">
<a href="https://github.com/pmmp/PocketMine-MP/actions/workflows/main.yml"><img src="https://github.com/pmmp/PocketMine-MP/workflows/CI/badge.svg" alt="CI" /></a>
<a href="https://github.com/pmmp/PocketMine-MP/actions/workflows/main.yml"><img src="https://github.com/pmmp/PocketMine-MP/actions/workflows/main.yml/badge.svg" alt="CI" /></a>
<a href="https://github.com/pmmp/PocketMine-MP/releases/latest"><img alt="GitHub release (latest SemVer)" src="https://img.shields.io/github/v/release/pmmp/PocketMine-MP?label=release&sort=semver"></a>
<a href="https://discord.gg/bmSAZBG"><img src="https://img.shields.io/discord/373199722573201408?label=discord&color=7289DA&logo=discord" alt="Discord" /></a>
<br>

25
changelogs/5.29.md Normal file
View File

@ -0,0 +1,25 @@
# 5.29.0
Released 18th June 2025.
This is a support release for Minecraft: Bedrock Edition 1.21.90.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Added support for Minecraft: Bedrock Edition 1.21.90.
- Removed support for earlier versions.
## Fixes
- Fixed thread crashes sometimes not reporting proper cause information in crashdumps.
- Fixed crash when a plugin replaced a player's held tool with a different tool with a damage exceeding the old tool's max damage during an action.
- Fixed performance issue of `PlayerAuthInputPacket` input flags handling (broken change detection).
- Fixed `BaseInventory->addItem()` triggering updates on empty slots when no items were added.
- Fixed slow check in `SubChunk` block layer garbage collection.
## Internals
- `LoginPacketHandler->processLogin()` signature has changed. This will break any plugins overriding `LoginPacketHandler`. As noted above, this is _not_ covered by the API version guarantee.
- Automated branch sync for `minor-next` and `major-next` is now triggered by `repository_dispatch` from a cron job in this repository instead of `RestrictedActions`. The `RestrictedActions` cron job was getting automatically disabled by GitHub due to repo inactivity.

73
changelogs/5.30.md Normal file
View File

@ -0,0 +1,73 @@
# 5.30.0
Released 18th June 2025.
This is a minor feature release containing API additions, internals cleanup and user experience improvements.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Significantly reduced log spam when unknown blocks, tiles and entities are found in saved worlds.
- The file name structure for crashdumps has been changed to improve sorting order in file browsers.
- Buffering is now skipped on the RakLib layer. In theory this could reduce player network latency by 10 ms (YMMV).
## Gameplay
### Blocks
- Many blocks have had their hardness and blast resistance updated to match vanilla.
- Implemented Respawn Anchor.
- Melon Stem and Pumpkin Stem drop amounts should now match vanilla (using binomial distribution).
## API
## General
- Verification of save registration has been added for blocks, entities and tiles. This is intended to make it easier to find mistakes when registering custom things, which previously would produce obscure core crashes.
### `pocketmine\event\block`
- The following classes have been added:
- `BlockPreExplodeEvent` - called before a block tries to explode
- `BlockExplodeEvent` - called when after a block's explosion calculation has been done, but before any changes are applied
### `pocketmine\event\entity`
- The following classes have been added:
- `EntityExtinguishEvent` - called when a burning entity is extinguished by water or other sources
- `EntityFrostWalkerEvent` - called every tick upon which an entity wearing Frost Walker boots moves; this can be used to customise or cancel the behaviour of the Frost Walker enchantment
### `pocketmine\event\player`
- The following classes have been added:
- `PlayerRespawnAnchorUseEvent` - called when a player interacts with a charged respawn anchor
### `pocketmine\entity`
- The following methods have been added:
- `public Entity->getStepHeight() : float`
- `public Entity->setStepHeight(float $stepHeight) : void`
### `pocketmine\world\generator`
- Generator execution has been decoupled from `PopulationTask` and async tasks in general. The following classes have been added:
- `executor\GeneratorExecutor`
- `executor\SyncGeneratorExecutor` - runs a generator on the main thread (used for flat world generation, which doesn't need threads)
- `executor\AsyncGeneratorExecutor` - runs a generator inside an async task, as before
- `PopulationUtils` - contains population business logic previously baked into `PopulationTask` - this permits the reuse of that logic outside async tasks
- The following methods have signature changes:
- `GeneratorManager->addGenerator()` now accepts an optional `bool $fast` parameter, defaulting to `false`; setting this to `true` will cause your generator to run on the main thread
- The following methods have been added:
- `public GeneratorManagerEntry->isFast() : bool` - returns whether this generator should run on the main thread
- `PopulationTask` has been marked as `@internal`. In the next major version, it will move to the `generator\executor` namespace; however, for now it stays put because plugins currently have no other way to regenerate chunks.
## Internals
- World data version numbers have been consolidated in `pocketmine\data\bedrock\WorldDataVersions`. This removes the need to modify several different files to support new world versions, and reduces the chances of things getting missed.
- Block hardness and blast resistance is now unit-tested against `block_properties_table.json` in `BedrockData`. This file comes from vanilla BDS, so we can use it to verify compliance.
- Protocol-layer "server auth block breaking" has been enabled. Functionally, this is no different from the previous system, it just works differently on the network layer.
- Various internal classes in the `pocketmine\world\generator` namespace have been moved to the `generator\executor` namespace.
- Removed `World->registerGenerator()` and `World->unregisterGenerator()`.
- Removed redundant calls to `curl_close()` (obsolete since PHP 8.0).
# 5.30.1
Released 23rd June 2025.
## Fixes
- Fixed accidental break of backwards compatibility in `EntityExplodeEvent` introduced in the previous release.
- Fixed placement of player holding block when exploding respawn anchor.
- Updated BedrockProtocol to fix incorrect encoding of `ServerScriptDebugDrawerPacket`.
- Disabled client-side locator bar, allowing plugins to write their own implementations.

14
changelogs/5.31.md Normal file
View File

@ -0,0 +1,14 @@
# 5.31.0
Released 8th July 2025.
This is a support release for Minecraft: Bedrock Edition 1.21.93.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Added support for Minecraft: Bedrock Edition 1.21.93.
- Removed support for earlier versions.

17
changelogs/5.32.md Normal file
View File

@ -0,0 +1,17 @@
# 5.32.0
Released 6th August 2025.
This is a support release for Minecraft: Bedrock Edition 1.21.100.
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
Do not update plugin minimum API versions unless you need new features added in this release.
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
## General
- Added support for Minecraft: Bedrock Edition 1.21.100.
- Removed support for earlier versions.
## Fixes
- Fixed deadlock on RakLib thread crash (e.g. due to port binding failure).

View File

@ -34,9 +34,9 @@
"adhocore/json-comment": "~1.2.0",
"netresearch/jsonmapper": "~v5.0.0",
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
"pocketmine/bedrock-data": "~5.0.0+bedrock-1.21.80",
"pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
"pocketmine/bedrock-protocol": "~38.1.0+bedrock-1.21.80",
"pocketmine/bedrock-data": "~5.3.0+bedrock-1.21.100",
"pocketmine/bedrock-item-upgrade-schema": "~1.15.0+bedrock-1.21.100",
"pocketmine/bedrock-protocol": "~40.0.0+bedrock-1.21.100",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0",
@ -48,7 +48,7 @@
"pocketmine/raklib": "~1.2.0",
"pocketmine/raklib-ipc": "~1.0.0",
"pocketmine/snooze": "^0.5.0",
"ramsey/uuid": "~4.8.0",
"ramsey/uuid": "~4.9.0",
"symfony/filesystem": "~6.4.0"
},
"require-dev": {

75
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": "7c3052613e98e566d8b00ae3c9119057",
"content-hash": "402ad5667b1e636a8ec6acf2f1b5f055",
"packages": [
{
"name": "adhocore/json-comment",
@ -204,16 +204,16 @@
},
{
"name": "pocketmine/bedrock-data",
"version": "5.0.0+bedrock-1.21.80",
"version": "5.3.0+bedrock-1.21.100",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080"
"reference": "5279e76261df158d5af187cfaafc1618c1da9e3f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/e38d5ea19f794ec5216e5f96742237e8c4e7f080",
"reference": "e38d5ea19f794ec5216e5f96742237e8c4e7f080",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/5279e76261df158d5af187cfaafc1618c1da9e3f",
"reference": "5279e76261df158d5af187cfaafc1618c1da9e3f",
"shasum": ""
},
"type": "library",
@ -224,22 +224,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.21.80"
"source": "https://github.com/pmmp/BedrockData/tree/5.3.0+bedrock-1.21.100"
},
"time": "2025-05-09T14:15:18+00:00"
"time": "2025-07-30T22:07:56+00:00"
},
{
"name": "pocketmine/bedrock-item-upgrade-schema",
"version": "1.14.0",
"version": "1.15.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
"reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb"
"reference": "09e0dbe9743f21a76b1fe04b2b4136785775f52b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/9fc7c9bbb558a017395c1cb7dd819c033ee971bb",
"reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/09e0dbe9743f21a76b1fe04b2b4136785775f52b",
"reference": "09e0dbe9743f21a76b1fe04b2b4136785775f52b",
"shasum": ""
},
"type": "library",
@ -250,22 +250,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.14.0"
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.15.0"
},
"time": "2024-12-04T12:22:49+00:00"
"time": "2025-08-06T15:08:48+00:00"
},
{
"name": "pocketmine/bedrock-protocol",
"version": "38.1.0+bedrock-1.21.80",
"version": "40.0.0+bedrock-1.21.100",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "a1fa215563517050045309bb779a67f75843b867"
"reference": "5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a1fa215563517050045309bb779a67f75843b867",
"reference": "a1fa215563517050045309bb779a67f75843b867",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca",
"reference": "5e95cab3a6e6c24920e0c25ca4aaf887ed4b70ca",
"shasum": ""
},
"require": {
@ -296,9 +296,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
"source": "https://github.com/pmmp/BedrockProtocol/tree/38.1.0+bedrock-1.21.80"
"source": "https://github.com/pmmp/BedrockProtocol/tree/40.0.0+bedrock-1.21.100"
},
"time": "2025-05-28T22:19:59+00:00"
"time": "2025-08-06T15:13:45+00:00"
},
{
"name": "pocketmine/binaryutils",
@ -818,21 +818,20 @@
},
{
"name": "ramsey/uuid",
"version": "4.8.1",
"version": "4.9.0",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28"
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28",
"reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0",
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0",
"shasum": ""
},
"require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13",
"ext-json": "*",
"php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0"
},
@ -891,9 +890,9 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.8.1"
"source": "https://github.com/ramsey/uuid/tree/4.9.0"
},
"time": "2025-06-01T06:28:46+00:00"
"time": "2025-06-25T14:20:11+00:00"
},
{
"name": "symfony/filesystem",
@ -1312,16 +1311,16 @@
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "2.0.4",
"version": "2.0.6",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a"
"reference": "f9f77efa9de31992a832ff77ea52eb42d675b094"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
"reference": "3e139cbe67fafa3588e1dbe27ca50f31fdb6236a",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/f9f77efa9de31992a832ff77ea52eb42d675b094",
"reference": "f9f77efa9de31992a832ff77ea52eb42d675b094",
"shasum": ""
},
"require": {
@ -1354,9 +1353,9 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.4"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.6"
},
"time": "2025-03-18T11:42:40+00:00"
"time": "2025-07-21T12:19:29+00:00"
},
{
"name": "phpunit/php-code-coverage",
@ -1681,16 +1680,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.46",
"version": "10.5.47",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "8080be387a5be380dda48c6f41cee4a13aadab3d"
"reference": "3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8080be387a5be380dda48c6f41cee4a13aadab3d",
"reference": "8080be387a5be380dda48c6f41cee4a13aadab3d",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3",
"reference": "3637b3e50d32ab3a0d1a33b3b6177169ec3d95a3",
"shasum": ""
},
"require": {
@ -1762,7 +1761,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.46"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.47"
},
"funding": [
{
@ -1786,7 +1785,7 @@
"type": "tidelift"
}
],
"time": "2025-05-02T06:46:24+00:00"
"time": "2025-06-20T11:29:11+00:00"
},
{
"name": "sebastian/cli-parser",

View File

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

View File

@ -85,7 +85,7 @@ final class RespawnAnchor extends Opaque{
switch($ev->getAction()){
case PlayerRespawnAnchorUseEvent::ACTION_EXPLODE:
$this->explode($player);
return false;
return true;
case PlayerRespawnAnchorUseEvent::ACTION_SET_SPAWN:
if($player->getSpawn() !== null && $player->getSpawn()->equals($this->position)){

View File

@ -123,7 +123,7 @@ abstract class Command{
}
if($this->permissionMessage === null){
$target->sendMessage(KnownTranslationFactory::pocketmine_command_error_permission($this->name)->baseTextFormat(TextFormat::RED));
$target->sendMessage(KnownTranslationFactory::pocketmine_command_error_permission($this->name)->prefix(TextFormat::RED));
}elseif($this->permissionMessage !== ""){
$target->sendMessage(str_replace("<permission>", $permission ?? implode(";", $this->permission), $this->permissionMessage));
}
@ -237,7 +237,7 @@ abstract class Command{
public static function broadcastCommandMessage(CommandSender $source, Translatable|string $message, bool $sendToSource = true) : void{
$users = $source->getServer()->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_ADMINISTRATIVE);
$result = KnownTranslationFactory::chat_type_admin($source->getName(), $message);
$colored = $result->baseTextFormat(TextFormat::GRAY . TextFormat::ITALIC);
$colored = $result->prefix(TextFormat::GRAY . TextFormat::ITALIC);
if($sendToSource){
$source->sendMessage($message);

View File

@ -107,7 +107,7 @@ class FormattedCommandAlias extends Command{
$timings->stopTiming();
}
}else{
$sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel, "/help")->baseTextFormat(TextFormat::RED)));
$sender->sendMessage($sender->getLanguage()->translate(KnownTranslationFactory::pocketmine_command_notFound($commandLabel, "/help")->prefix(TextFormat::RED)));
//to match the behaviour of SimpleCommandMap::dispatch()
//this shouldn't normally happen, but might happen if the command was unregistered or modified after

View File

@ -226,7 +226,7 @@ class SimpleCommandMap implements CommandMap{
return true;
}
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($sentCommandLabel ?? "", "/help")->baseTextFormat(TextFormat::RED));
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($sentCommandLabel ?? "", "/help")->prefix(TextFormat::RED));
return false;
}

View File

@ -69,7 +69,7 @@ class ClearCommand extends VanillaCommand{
}
}catch(LegacyStringToItemParserException $e){
//vanilla checks this at argument parsing layer, can't come up with a better alternative
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->baseTextFormat(TextFormat::RED));
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->prefix(TextFormat::RED));
return true;
}
}
@ -90,7 +90,7 @@ class ClearCommand extends VanillaCommand{
if($count > 0){
$sender->sendMessage(KnownTranslationFactory::commands_clear_testing($target->getName(), (string) $count));
}else{
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->baseTextFormat(TextFormat::RED));
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->prefix(TextFormat::RED));
}
return true;
@ -132,7 +132,7 @@ class ClearCommand extends VanillaCommand{
if($clearedCount > 0){
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_clear_success($target->getName(), (string) $clearedCount));
}else{
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->baseTextFormat(TextFormat::RED));
$sender->sendMessage(KnownTranslationFactory::commands_clear_failure_no_items($target->getName())->prefix(TextFormat::RED));
}
return true;

View File

@ -68,7 +68,7 @@ class EffectCommand extends VanillaCommand{
$effect = StringToEffectParser::getInstance()->parse($args[1]);
if($effect === null){
$sender->sendMessage(KnownTranslationFactory::commands_effect_notFound($args[1])->baseTextFormat(TextFormat::RED));
$sender->sendMessage(KnownTranslationFactory::commands_effect_notFound($args[1])->prefix(TextFormat::RED));
return true;
}

View File

@ -66,7 +66,7 @@ class GiveCommand extends VanillaCommand{
try{
$item = StringToItemParser::getInstance()->parse($args[1]) ?? LegacyStringToItemParser::getInstance()->parse($args[1]);
}catch(LegacyStringToItemParserException $e){
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->baseTextFormat(TextFormat::RED));
$sender->sendMessage(KnownTranslationFactory::commands_give_item_notFound($args[1])->prefix(TextFormat::RED));
return true;
}
@ -101,7 +101,7 @@ class GiveCommand extends VanillaCommand{
$player->getInventory()->addItem($item);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_give_success(
$item->getName() . TextFormat::RESET . " (" . $args[1] . ")",
$item->getName() . " (" . $args[1] . ")",
(string) $item->getCount(),
$player->getName()
));

View File

@ -105,22 +105,22 @@ class HelpCommand extends VanillaCommand{
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getLabel())
->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------"));
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString)
->baseTextFormat(TextFormat::GOLD));
->prefix(TextFormat::GOLD));
$usage = $cmd->getUsage();
$usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage;
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString, limit: PHP_INT_MAX)))
->baseTextFormat(TextFormat::GOLD));
->prefix(TextFormat::GOLD));
$aliases = $cmd->getAliases();
sort($aliases, SORT_NATURAL);
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases))
->baseTextFormat(TextFormat::GOLD));
->prefix(TextFormat::GOLD));
return true;
}
}
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($commandName, "/help")->baseTextFormat(TextFormat::RED));
$sender->sendMessage(KnownTranslationFactory::pocketmine_command_notFound($commandName, "/help")->prefix(TextFormat::RED));
return true;
}

View File

@ -114,7 +114,7 @@ class ParticleCommand extends VanillaCommand{
$particle = $this->getParticle($name, $data);
if($particle === null){
$sender->sendMessage(KnownTranslationFactory::commands_particle_notFound($name)->baseTextFormat(TextFormat::RED));
$sender->sendMessage(KnownTranslationFactory::commands_particle_notFound($name)->prefix(TextFormat::RED));
return true;
}

View File

@ -52,7 +52,7 @@ class SayCommand extends VanillaCommand{
$sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_announcement(
$sender instanceof Player ? $sender->getDisplayName() : ($sender instanceof ConsoleCommandSender ? "Server" : $sender->getName()),
implode(" ", $args)
)->baseTextFormat(TextFormat::LIGHT_PURPLE));
)->prefix(TextFormat::LIGHT_PURPLE));
return true;
}
}

View File

@ -60,9 +60,9 @@ class TellCommand extends VanillaCommand{
if($player instanceof Player){
$message = implode(" ", $args);
$sender->sendMessage(KnownTranslationFactory::commands_message_display_outgoing($player->getDisplayName(), $message)->baseTextFormat(TextFormat::GRAY . TextFormat::ITALIC));
$sender->sendMessage(KnownTranslationFactory::commands_message_display_outgoing($player->getDisplayName(), $message)->prefix(TextFormat::GRAY . TextFormat::ITALIC));
$name = $sender instanceof Player ? $sender->getDisplayName() : $sender->getName();
$player->sendMessage(KnownTranslationFactory::commands_message_display_incoming($name, $message)->baseTextFormat(TextFormat::GRAY . TextFormat::ITALIC));
$player->sendMessage(KnownTranslationFactory::commands_message_display_incoming($name, $message)->prefix(TextFormat::GRAY . TextFormat::ITALIC));
Command::broadcastCommandMessage($sender, KnownTranslationFactory::commands_message_display_outgoing($player->getDisplayName(), $message), false);
}else{
$sender->sendMessage(KnownTranslationFactory::commands_generic_player_notFound());

View File

@ -99,11 +99,11 @@ abstract class VanillaCommand extends Command{
$v = (int) $input;
if($v > $max){
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooBig($input, (string) $max)->baseTextFormat(TextFormat::RED));
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooBig($input, (string) $max)->prefix(TextFormat::RED));
return null;
}
if($v < $min){
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooSmall($input, (string) $min)->baseTextFormat(TextFormat::RED));
$sender->sendMessage(KnownTranslationFactory::commands_generic_num_tooSmall($input, (string) $min)->prefix(TextFormat::RED));
return null;
}

View File

@ -54,13 +54,13 @@ final class WorldDataVersions{
* This may be lower than the current protocol version if PocketMine-MP does not yet support features of the newer
* version. This allows the protocol to be updated independently of world format support.
*/
public const NETWORK = 800;
public const NETWORK = 827;
public const LAST_OPENED_IN = [
1, //major
21, //minor
80, //patch
3, //revision
100, //patch
23, //revision
0 //is beta
];
}

View File

@ -255,6 +255,7 @@ final class BlockTypeNames{
public const CONDUIT = "minecraft:conduit";
public const COPPER_BLOCK = "minecraft:copper_block";
public const COPPER_BULB = "minecraft:copper_bulb";
public const COPPER_CHEST = "minecraft:copper_chest";
public const COPPER_DOOR = "minecraft:copper_door";
public const COPPER_GRATE = "minecraft:copper_grate";
public const COPPER_ORE = "minecraft:copper_ore";
@ -533,6 +534,7 @@ final class BlockTypeNames{
public const EXPOSED_CHISELED_COPPER = "minecraft:exposed_chiseled_copper";
public const EXPOSED_COPPER = "minecraft:exposed_copper";
public const EXPOSED_COPPER_BULB = "minecraft:exposed_copper_bulb";
public const EXPOSED_COPPER_CHEST = "minecraft:exposed_copper_chest";
public const EXPOSED_COPPER_DOOR = "minecraft:exposed_copper_door";
public const EXPOSED_COPPER_GRATE = "minecraft:exposed_copper_grate";
public const EXPOSED_COPPER_TRAPDOOR = "minecraft:exposed_copper_trapdoor";
@ -857,6 +859,7 @@ final class BlockTypeNames{
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";
public const OXIDIZED_COPPER_CHEST = "minecraft:oxidized_copper_chest";
public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door";
public const OXIDIZED_COPPER_GRATE = "minecraft:oxidized_copper_grate";
public const OXIDIZED_COPPER_TRAPDOOR = "minecraft:oxidized_copper_trapdoor";
@ -1211,6 +1214,7 @@ final class BlockTypeNames{
public const WAXED_CHISELED_COPPER = "minecraft:waxed_chiseled_copper";
public const WAXED_COPPER = "minecraft:waxed_copper";
public const WAXED_COPPER_BULB = "minecraft:waxed_copper_bulb";
public const WAXED_COPPER_CHEST = "minecraft:waxed_copper_chest";
public const WAXED_COPPER_DOOR = "minecraft:waxed_copper_door";
public const WAXED_COPPER_GRATE = "minecraft:waxed_copper_grate";
public const WAXED_COPPER_TRAPDOOR = "minecraft:waxed_copper_trapdoor";
@ -1221,6 +1225,7 @@ final class BlockTypeNames{
public const WAXED_EXPOSED_CHISELED_COPPER = "minecraft:waxed_exposed_chiseled_copper";
public const WAXED_EXPOSED_COPPER = "minecraft:waxed_exposed_copper";
public const WAXED_EXPOSED_COPPER_BULB = "minecraft:waxed_exposed_copper_bulb";
public const WAXED_EXPOSED_COPPER_CHEST = "minecraft:waxed_exposed_copper_chest";
public const WAXED_EXPOSED_COPPER_DOOR = "minecraft:waxed_exposed_copper_door";
public const WAXED_EXPOSED_COPPER_GRATE = "minecraft:waxed_exposed_copper_grate";
public const WAXED_EXPOSED_COPPER_TRAPDOOR = "minecraft:waxed_exposed_copper_trapdoor";
@ -1231,6 +1236,7 @@ final class BlockTypeNames{
public const WAXED_OXIDIZED_CHISELED_COPPER = "minecraft:waxed_oxidized_chiseled_copper";
public const WAXED_OXIDIZED_COPPER = "minecraft:waxed_oxidized_copper";
public const WAXED_OXIDIZED_COPPER_BULB = "minecraft:waxed_oxidized_copper_bulb";
public const WAXED_OXIDIZED_COPPER_CHEST = "minecraft:waxed_oxidized_copper_chest";
public const WAXED_OXIDIZED_COPPER_DOOR = "minecraft:waxed_oxidized_copper_door";
public const WAXED_OXIDIZED_COPPER_GRATE = "minecraft:waxed_oxidized_copper_grate";
public const WAXED_OXIDIZED_COPPER_TRAPDOOR = "minecraft:waxed_oxidized_copper_trapdoor";
@ -1241,6 +1247,7 @@ final class BlockTypeNames{
public const WAXED_WEATHERED_CHISELED_COPPER = "minecraft:waxed_weathered_chiseled_copper";
public const WAXED_WEATHERED_COPPER = "minecraft:waxed_weathered_copper";
public const WAXED_WEATHERED_COPPER_BULB = "minecraft:waxed_weathered_copper_bulb";
public const WAXED_WEATHERED_COPPER_CHEST = "minecraft:waxed_weathered_copper_chest";
public const WAXED_WEATHERED_COPPER_DOOR = "minecraft:waxed_weathered_copper_door";
public const WAXED_WEATHERED_COPPER_GRATE = "minecraft:waxed_weathered_copper_grate";
public const WAXED_WEATHERED_COPPER_TRAPDOOR = "minecraft:waxed_weathered_copper_trapdoor";
@ -1251,6 +1258,7 @@ final class BlockTypeNames{
public const WEATHERED_CHISELED_COPPER = "minecraft:weathered_chiseled_copper";
public const WEATHERED_COPPER = "minecraft:weathered_copper";
public const WEATHERED_COPPER_BULB = "minecraft:weathered_copper_bulb";
public const WEATHERED_COPPER_CHEST = "minecraft:weathered_copper_chest";
public const WEATHERED_COPPER_DOOR = "minecraft:weathered_copper_door";
public const WEATHERED_COPPER_GRATE = "minecraft:weathered_copper_grate";
public const WEATHERED_COPPER_TRAPDOOR = "minecraft:weathered_copper_trapdoor";

View File

@ -153,8 +153,19 @@ final class ItemTypeNames{
public const COOKED_RABBIT = "minecraft:cooked_rabbit";
public const COOKED_SALMON = "minecraft:cooked_salmon";
public const COOKIE = "minecraft:cookie";
public const COPPER_AXE = "minecraft:copper_axe";
public const COPPER_BOOTS = "minecraft:copper_boots";
public const COPPER_CHESTPLATE = "minecraft:copper_chestplate";
public const COPPER_DOOR = "minecraft:copper_door";
public const COPPER_GOLEM_SPAWN_EGG = "minecraft:copper_golem_spawn_egg";
public const COPPER_HELMET = "minecraft:copper_helmet";
public const COPPER_HOE = "minecraft:copper_hoe";
public const COPPER_INGOT = "minecraft:copper_ingot";
public const COPPER_LEGGINGS = "minecraft:copper_leggings";
public const COPPER_NUGGET = "minecraft:copper_nugget";
public const COPPER_PICKAXE = "minecraft:copper_pickaxe";
public const COPPER_SHOVEL = "minecraft:copper_shovel";
public const COPPER_SWORD = "minecraft:copper_sword";
public const CORAL = "minecraft:coral";
public const CORAL_BLOCK = "minecraft:coral_block";
public const CORAL_FAN = "minecraft:coral_fan";
@ -372,6 +383,7 @@ final class ItemTypeNames{
public const MUSIC_DISC_CREATOR = "minecraft:music_disc_creator";
public const MUSIC_DISC_CREATOR_MUSIC_BOX = "minecraft:music_disc_creator_music_box";
public const MUSIC_DISC_FAR = "minecraft:music_disc_far";
public const MUSIC_DISC_LAVA_CHICKEN = "minecraft:music_disc_lava_chicken";
public const MUSIC_DISC_MALL = "minecraft:music_disc_mall";
public const MUSIC_DISC_MELLOHI = "minecraft:music_disc_mellohi";
public const MUSIC_DISC_OTHERSIDE = "minecraft:music_disc_otherside";
@ -380,6 +392,7 @@ final class ItemTypeNames{
public const MUSIC_DISC_RELIC = "minecraft:music_disc_relic";
public const MUSIC_DISC_STAL = "minecraft:music_disc_stal";
public const MUSIC_DISC_STRAD = "minecraft:music_disc_strad";
public const MUSIC_DISC_TEARS = "minecraft:music_disc_tears";
public const MUSIC_DISC_WAIT = "minecraft:music_disc_wait";
public const MUSIC_DISC_WARD = "minecraft:music_disc_ward";
public const MUTTON = "minecraft:mutton";

View File

@ -51,7 +51,7 @@ class EntityExplodeEvent extends EntityEvent implements Cancellable{
protected Position $position,
protected array $blocks,
protected float $yield,
private array $ignitions
private array $ignitions = []
){
$this->entity = $entity;
if($yield < 0.0 || $yield > 100.0){

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\lang;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
use Symfony\Component\Filesystem\Path;
use function array_filter;
@ -142,26 +141,18 @@ class Language{
/**
* @param (float|int|string|Translatable)[] $params
*/
public function translateString(string $str, array $params = [], ?string $onlyPrefix = null, string $baseFormat = "") : string{
if($onlyPrefix !== null && !str_starts_with($str, $onlyPrefix)){
//plain key for client-side translation
//% is added here if we add base format since this will turn into an embedded key
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, "%" . $str) : $str;
}
$baseText = $this->internalGet($str);
if($baseText === null){ //key not found, embedded inside format string with %, or doesn't match prefix
public function translateString(string $str, array $params = [], ?string $onlyPrefix = null) : string{
$baseText = ($onlyPrefix === null || str_starts_with($str, $onlyPrefix)) ? $this->internalGet($str) : null;
if($baseText === null){ //key not found, embedded inside format string, or doesn't match prefix
$baseText = $this->parseTranslation($str, $onlyPrefix);
}
foreach(Utils::promoteKeys($params) as $i => $p){
$replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p;
if($baseFormat !== ""){
$replacement = TextFormat::addBase($baseFormat, $replacement) . TextFormat::RESET;
}
$baseText = str_replace("{%$i}", $replacement, $baseText);
}
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, $baseText) : $baseText;
return $baseText;
}
public function translate(Translatable $c) : string{
@ -170,17 +161,12 @@ class Language{
$baseText = $this->parseTranslation($c->getText());
}
$baseFormat = $c->getBaseFormat();
foreach(Utils::promoteKeys($c->getParameters()) as $i => $p){
$replacement = $p instanceof Translatable ? $this->translate($p) : $p;
if($baseFormat !== ""){
$replacement = TextFormat::addBase($baseFormat, $replacement) . TextFormat::RESET;
}
$baseText = str_replace("{%$i}", $replacement, $baseText);
}
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, $baseText) : $baseText;
return $baseText;
}
protected function internalGet(string $id) : ?string{

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\lang;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
final class Translatable{
@ -35,8 +34,7 @@ final class Translatable{
*/
public function __construct(
protected string $text,
array $params = [],
private string $baseFormat = ""
array $params = []
){
foreach(Utils::promoteKeys($params) as $k => $param){
if(!($param instanceof Translatable)){
@ -62,8 +60,6 @@ final class Translatable{
return $this->params[$i] ?? null;
}
public function getBaseFormat() : string{ return $this->baseFormat; }
public function format(string $before, string $after) : self{
return new self("$before%$this->text$after", $this->params);
}
@ -75,12 +71,4 @@ final class Translatable{
public function postfix(string $postfix) : self{
return new self("%$this->text" . $postfix);
}
/**
* Sets the base format to be applied to the translation result by {@link TextFormat::addBase()}.
* Any existing base format is overwritten.
*/
public function baseTextFormat(string $baseFormat) : self{
return new self($this->text, $this->params, $baseFormat);
}
}

View File

@ -767,7 +767,7 @@ class NetworkSession{
$errorId = implode("-", str_split(bin2hex(random_bytes(6)), 4));
$this->disconnect(
reason: KnownTranslationFactory::pocketmine_disconnect_error($reason, $errorId)->baseTextFormat(TextFormat::RED),
reason: KnownTranslationFactory::pocketmine_disconnect_error($reason, $errorId)->prefix(TextFormat::RED),
disconnectScreenMessage: KnownTranslationFactory::pocketmine_disconnect_error($disconnectScreenMessage ?? $reason, $errorId),
);
}
@ -1130,12 +1130,8 @@ class NetworkSession{
public function prepareClientTranslatableMessage(Translatable $message) : array{
//we can't send nested translations to the client, so make sure they are always pre-translated by the server
$language = $this->player->getLanguage();
$baseFormat = $message->getBaseFormat();
$parameters = array_map(function(string|Translatable $p) use ($baseFormat, $language){
$string = $p instanceof Translatable ? $language->translate($p) : $p;
return $baseFormat !== "" ? TextFormat::addBase($baseFormat, $string) . TextFormat::RESET : $string;
}, $message->getParameters());
return [$language->translateString($message->getText(), $parameters, "pocketmine.", $baseFormat), $parameters];
$parameters = array_map(fn(string|Translatable $p) => $p instanceof Translatable ? $language->translate($p) : $p, $message->getParameters());
return [$language->translateString($message->getText(), $parameters, "pocketmine."), $parameters];
}
public function onChatMessage(Translatable|string $message) : void{

View File

@ -33,6 +33,8 @@ use pocketmine\network\mcpe\JwtUtils;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\LoginPacket;
use pocketmine\network\mcpe\protocol\types\login\AuthenticationData;
use pocketmine\network\mcpe\protocol\types\login\AuthenticationInfo;
use pocketmine\network\mcpe\protocol\types\login\AuthenticationType;
use pocketmine\network\mcpe\protocol\types\login\ClientData;
use pocketmine\network\mcpe\protocol\types\login\ClientDataToSkinDataHelper;
use pocketmine\network\mcpe\protocol\types\login\JwtChain;
@ -42,7 +44,11 @@ use pocketmine\player\PlayerInfo;
use pocketmine\player\XboxLivePlayerInfo;
use pocketmine\Server;
use Ramsey\Uuid\Uuid;
use function gettype;
use function is_array;
use function is_object;
use function json_decode;
use const JSON_THROW_ON_ERROR;
/**
* Handles the initial login phase of the session. This handler is used as the initial state.
@ -60,7 +66,9 @@ class LoginPacketHandler extends PacketHandler{
){}
public function handleLogin(LoginPacket $packet) : bool{
$extraData = $this->fetchAuthData($packet->chainDataJwt);
$authInfo = $this->parseAuthInfo($packet->authInfoJson);
$jwtChain = $this->parseJwtChain($authInfo->Certificate);
$extraData = $this->fetchAuthData($jwtChain);
if(!Player::isValidUserName($extraData->displayName)){
$this->session->disconnectWithError(KnownTranslationFactory::disconnectionScreen_invalidName());
@ -139,11 +147,61 @@ class LoginPacketHandler extends PacketHandler{
return true;
}
$this->processLogin($packet, $ev->isAuthRequired());
$this->processLogin($authInfo->Token, AuthenticationType::from($authInfo->AuthenticationType), $jwtChain->chain, $packet->clientDataJwt, $ev->isAuthRequired());
return true;
}
/**
* @throws PacketHandlingException
*/
protected function parseAuthInfo(string $authInfo) : AuthenticationInfo{
try{
$authInfoJson = json_decode($authInfo, associative: false, flags: JSON_THROW_ON_ERROR);
}catch(\JsonException $e){
throw PacketHandlingException::wrap($e);
}
if(!is_object($authInfoJson)){
throw new \RuntimeException("Unexpected type for auth info data: " . gettype($authInfoJson) . ", expected object");
}
$mapper = new \JsonMapper();
$mapper->bExceptionOnMissingData = true;
$mapper->bExceptionOnUndefinedProperty = true;
$mapper->bStrictObjectTypeChecking = true;
try{
$clientData = $mapper->map($authInfoJson, new AuthenticationInfo());
}catch(\JsonMapper_Exception $e){
throw PacketHandlingException::wrap($e);
}
return $clientData;
}
/**
* @throws PacketHandlingException
*/
protected function parseJwtChain(string $chainDataJwt) : JwtChain{
try{
$jwtChainJson = json_decode($chainDataJwt, associative: false, flags: JSON_THROW_ON_ERROR);
}catch(\JsonException $e){
throw PacketHandlingException::wrap($e);
}
if(!is_object($jwtChainJson)){
throw new \RuntimeException("Unexpected type for JWT chain data: " . gettype($jwtChainJson) . ", expected object");
}
$mapper = new \JsonMapper();
$mapper->bExceptionOnMissingData = true;
$mapper->bExceptionOnUndefinedProperty = true;
$mapper->bStrictObjectTypeChecking = true;
try{
$clientData = $mapper->map($jwtChainJson, new JwtChain());
}catch(\JsonMapper_Exception $e){
throw PacketHandlingException::wrap($e);
}
return $clientData;
}
/**
* @throws PacketHandlingException
*/
@ -211,10 +269,15 @@ class LoginPacketHandler extends PacketHandler{
* TODO: This is separated for the purposes of allowing plugins (like Specter) to hack it and bypass authentication.
* In the future this won't be necessary.
*
* @param null|string[] $legacyCertificate
*
* @throws \InvalidArgumentException
*/
protected function processLogin(LoginPacket $packet, bool $authRequired) : void{
$this->server->getAsyncPool()->submitTask(new ProcessLoginTask($packet->chainDataJwt->chain, $packet->clientDataJwt, $authRequired, $this->authCallback));
protected function processLogin(string $token, AuthenticationType $authType, ?array $legacyCertificate, string $clientData, bool $authRequired) : void{
if($legacyCertificate === null){
throw new PacketHandlingException("Legacy certificate cannot be null");
}
$this->server->getAsyncPool()->submitTask(new ProcessLoginTask($legacyCertificate, $clientData, $authRequired, $this->authCallback));
$this->session->setHandler(null); //drop packets received during login verification
}
}

View File

@ -40,7 +40,6 @@ use pocketmine\network\mcpe\protocol\types\Experiments;
use pocketmine\network\mcpe\protocol\types\LevelSettings;
use pocketmine\network\mcpe\protocol\types\NetworkPermissions;
use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings;
use pocketmine\network\mcpe\protocol\types\ServerAuthMovementMode;
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
use pocketmine\player\Player;
use pocketmine\Server;
@ -82,7 +81,8 @@ class PreSpawnPacketHandler extends PacketHandler{
$levelSettings->lightningLevel = 0;
$levelSettings->commandsEnabled = true;
$levelSettings->gameRules = [
"naturalregeneration" => new BoolGameRule(false, false) //Hack for client side regeneration
"naturalregeneration" => new BoolGameRule(false, false), //Hack for client side regeneration
"locatorbar" => new BoolGameRule(false, false) //Disable client-side tracking of nearby players
];
$levelSettings->experiments = new Experiments([], false);
@ -99,7 +99,7 @@ class PreSpawnPacketHandler extends PacketHandler{
$this->server->getMotd(),
"",
false,
new PlayerMovementSettings(ServerAuthMovementMode::SERVER_AUTHORITATIVE_V3, 0, true),
new PlayerMovementSettings(0, true),
0,
0,
"",
@ -108,6 +108,7 @@ class PreSpawnPacketHandler extends PacketHandler{
Uuid::fromString(Uuid::NIL),
false,
false,
false,
new NetworkPermissions(disableClientSounds: true),
[],
0,

View File

@ -120,7 +120,8 @@ class ResourcePacksPacketHandler extends PacketHandler{
hasAddons: false,
hasScripts: false,
worldTemplateId: Uuid::fromString(Uuid::NIL),
worldTemplateVersion: ""
worldTemplateVersion: "",
forceDisableVibrantVisuals: true,
));
$this->session->getLogger()->debug("Waiting for client to accept resource packs");
}

View File

@ -68,17 +68,17 @@ class RakLibServer extends Thread{
public function startAndWait(int $options = NativeThread::INHERIT_NONE) : void{
$this->start($options);
$this->synchronized(function() : void{
while(!$this->ready && $this->getCrashInfo() === null){
while(!$this->ready && !$this->isTerminated()){
$this->wait();
}
$crashInfo = $this->getCrashInfo();
if($crashInfo !== null){
if($crashInfo->getType() === SocketException::class){
throw new SocketException($crashInfo->getMessage());
}
throw new ThreadCrashException("RakLib failed to start", $crashInfo);
}
});
$crashInfo = $this->getCrashInfo();
if($crashInfo !== null){
if($crashInfo->getType() === SocketException::class){
throw new SocketException($crashInfo->getMessage());
}
throw new ThreadCrashException("RakLib failed to start", $crashInfo);
}
}
protected function onRun() : void{

View File

@ -411,7 +411,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
public function getLeaveMessage() : Translatable|string{
if($this->spawned){
return KnownTranslationFactory::multiplayer_player_left($this->getDisplayName())->baseTextFormat(TextFormat::YELLOW);
return KnownTranslationFactory::multiplayer_player_left($this->getDisplayName())->prefix(TextFormat::YELLOW);
}
return "";
@ -946,7 +946,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
});
$ev = new PlayerJoinEvent($this,
KnownTranslationFactory::multiplayer_player_joined($this->getDisplayName())->baseTextFormat(TextFormat::YELLOW)
KnownTranslationFactory::multiplayer_player_joined($this->getDisplayName())->prefix(TextFormat::YELLOW)
);
$ev->call();
if($ev->getJoinMessage() !== ""){

View File

@ -100,6 +100,8 @@ trait CommonThreadPartsTrait{
//*before* the shutdown handler is invoked, so we might land here before the crash info has been set.
//In the future this should probably be fixed by running the shutdown handlers before setting isTerminated,
//but this workaround should be good enough for now.
//WARNING: Do not call this inside a synchronized block on this thread's context. Because the shutdown handler
//runs in a synchronized block, this will result in a deadlock.
if($this->isTerminated() && !$this->isJoined()){
$this->join();
}

View File

@ -190,10 +190,8 @@ abstract class TextFormat{
* - Base format "§c" (red) + "Hello" (no format) = "§r§cHello"
* - Base format "§c" + "Hello §rWorld" = "§r§cHello §r§cWorld"
*
* Note: Adding base formatting to the output string a second time won't override conflicting formatting from the
* earlier call (e.g. adding base format BLUE to a string which already has YELLOW base formatting will
* still result in yellow text after any RESET code). However, complementary codes (e.g. italic, bold) will combine
* with the existing codes (e.g. adding ITALIC to a string with base format YELLOW will give yellow & italic text).
* Note: Adding base formatting to the output string a second time will result in a combination of formats from both
* calls. This is not by design, but simply a consequence of the way the function is implemented.
*/
public static function addBase(string $baseFormat, string $string) : string{
$baseFormatParts = self::tokenize($baseFormat);

View File

@ -28,7 +28,7 @@ namespace pocketmine\world\biome\model;
*/
final class BiomeDefinitionEntryData{
/** @required */
public ?int $id;
public int $id;
/** @required */
public float $temperature;

View File

@ -134,11 +134,8 @@ class SubChunk{
foreach($this->blockLayers as $layer){
$layer->collectGarbage();
foreach($layer->getPalette() as $p){
if($p !== $this->emptyBlockId){
$cleanedLayers[] = $layer;
continue 2;
}
if($layer->getBitsPerBlock() !== 0 || $layer->get(0, 0, 0) !== $this->emptyBlockId){
$cleanedLayers[] = $layer;
}
}
$this->blockLayers = $cleanedLayers;