mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-10-16 03:51:37 +00:00
Merge branch 'minor-next' into gameplay-permissions
This commit is contained in:
2
.github/workflows/discord-release-notify.yml
vendored
2
.github/workflows/discord-release-notify.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
uses: shivammathur/setup-php@2.35.5
|
||||
with:
|
||||
php-version: 8.2
|
||||
|
||||
|
2
.github/workflows/draft-release-pr-check.yml
vendored
2
.github/workflows/draft-release-pr-check.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
uses: shivammathur/setup-php@2.35.5
|
||||
with:
|
||||
php-version: 8.2
|
||||
|
||||
|
4
.github/workflows/draft-release.yml
vendored
4
.github/workflows/draft-release.yml
vendored
@@ -87,7 +87,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
uses: shivammathur/setup-php@2.35.5
|
||||
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.18.0
|
||||
uses: ncipollo/release-action@v1.20.0
|
||||
id: create-draft
|
||||
with:
|
||||
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst
|
||||
|
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup PHP and tools
|
||||
uses: shivammathur/setup-php@2.35.4
|
||||
uses: shivammathur/setup-php@2.35.5
|
||||
with:
|
||||
php-version: 8.3
|
||||
tools: php-cs-fixer:3.75
|
||||
|
@@ -11,7 +11,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Remove label
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
|
2
.github/workflows/pr-stale.yml
vendored
2
.github/workflows/pr-stale.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
|
@@ -85,6 +85,13 @@ Note that there might be a long wait time before a reviewer looks at your PR.
|
||||
Depending on the changes, maintainers might ask you to make changes to the PR to fix problems or to improve the code.
|
||||
**Do not delete your fork** while your pull request remains open, otherwise you won't be able to make any requested changes and the PR will end up being declined.
|
||||
|
||||
> [!TIP]
|
||||
> Don't worry about getting a PR perfect on the first try.
|
||||
> In fact, it's quite unusual for a PR to be perfect when it's first submitted, and most PRs will get changes requested by reviewers, even when the PR is made by one of our team members.
|
||||
>
|
||||
> Mistakes are normal, and PMMP team members will review your code and suggest changes to your code as needed.
|
||||
> Just make sure to stick with it so you can communicate with reviewers and/or make changes.
|
||||
|
||||
### Requirements
|
||||
The following are required as a minimum for pull requests. PRs that don't meet these requirements will be declined unless updated to meet them.
|
||||
|
||||
|
Submodule build/php updated: 8fe187335f...2714034579
111
changelogs/5.34.md
Normal file
111
changelogs/5.34.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# 5.34.0
|
||||
Released 26th September 2025.
|
||||
|
||||
This is a minor feature release containing performance improvements, new gameplay features, new API additions and network changes.
|
||||
|
||||
**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
|
||||
- PocketMine-MP now requires and uses [`pmmp/ext-encoding`](https://github.com/pmmp/ext-encoding) version `1.0.0`, a PHP extension designed to significantly improve performance of data encoding and decoding (@dktapps).
|
||||
- This first pass only implements support in low-risk areas, such as network protocol and read-only data. Further integration will follow in future minor versions with additional performance improvements.
|
||||
- While ext-encoding has been heavily tested, we can't be sure there won't be major issues once it reaches production. Please report any bugs you find on the GitHub issue tracker.
|
||||
- New Bedrock OpenID authentication support has been implemented (@dries-c, @dktapps).
|
||||
- This system fetches keys from an API provided by Microsoft. Therefore, your server must now have internet access to authenticate players.
|
||||
- `/timings paste` now creates private reports by default on `timings.pmmp.io` (@dktapps).
|
||||
- Private reports require an access token to view, so your timings reports can no longer be viewed by others just by guessing the ID.
|
||||
- If you're using a custom timings host, be sure to update it to get support for this feature.
|
||||
- The command will generate a warning in the console if the target timings host doesn't support private reports.
|
||||
|
||||
## Performance
|
||||
- Significantly improved performance of packet encoding and decoding using `ext-encoding` (@dktapps).
|
||||
- Unnecessary NBT is now stripped from items before sending them over the network. This significantly improves performance when working with writable books, shulker boxes, etc. (@dktapps).
|
||||
- Improved performance of item saving in `ItemSerializer` by avoiding slow `hasNamedTag()` call followed by `getNamedTag()` (both will rebuild the NBT) (@dktapps).
|
||||
|
||||
## Gameplay
|
||||
- Implemented basic Trident functionality (@IvanCraft623).
|
||||
- Implemented Firework and Firework Star (@IvanCraft623).
|
||||
- Editing the rear side of signs is now supported (@dktapps).
|
||||
- Sneaking hitbox height has been adjusted to match vanilla (@Dasciam).
|
||||
|
||||
## API
|
||||
### General
|
||||
- `pocketmine/nbt` version `1.2.0` is now used ([changelog](https://github.com/pmmp/NBT/releases/tag/1.2.0)).
|
||||
- `pmmp/ext-encoding` version `1.0.0` is now required and used.
|
||||
- This can be used as a faster alternative to `BinaryStream` and `Binary` in most use cases. However, please note that its API is very different, and it hasn't been battle-tested yet.
|
||||
- A recent JetBrains IDE stub can be found in our [custom stubs repository](https://github.com/pmmp/phpstorm-stubs/blob/fork/encoding/encoding.php).
|
||||
|
||||
### `pocketmine\block`
|
||||
- The following API methods have been added:
|
||||
- `public BaseSign->getFaceText(bool $frontFace) : SignText`
|
||||
- `public BaseSign->setFaceText(bool $frontFace, SignText $text) : $this`
|
||||
- `public BaseSign->updateFaceText(Player $author, bool $frontFace, SignText $text) : bool` - called by the network system when a player edits a sign, triggers `SignChangeEvent` etc.
|
||||
- `protected BaseSign->getHitboxCenter() : Vector3` - returns the center of the sign's hitbox, used to decide which face of the sign the player is editing
|
||||
- `protected BaseSign->getFacingDegrees() : float` (to become abstract in PM6) - returns the horizontal facing of the sign in degrees, used to decide which face of the sign the player is editing
|
||||
- The following API methods have been deprecated:
|
||||
- `public BaseSign->getText() : SignText` - use `getFaceText()` instead
|
||||
- `public BaseSign->setText(SignText $text) : $this` - use `setFaceText()` instead
|
||||
- `public BaseSign->updateText(Player $author, SignText $text) : bool` - use `updateFaceText()` instead
|
||||
|
||||
### `pocketmine\entity`
|
||||
- The following API classes have been added:
|
||||
- `NeverSavedWithChunkEntity` - implement this instead of overriding `canSaveWithChunk()` if your entity will never need a save ID
|
||||
- Used currently by `Player` and `FireworkRocket`.
|
||||
- `animation\FireworkParticlesAnimation`
|
||||
- `object\FireworkRocket`
|
||||
- `projectile\Trident`
|
||||
- The following API methods have been added:
|
||||
- `public Living->getSneakOffset() : float` - returns how much the entity's hitbox is shortened and eye height lowered when sneaking (0 by default)
|
||||
- `protected Projectile->despawnsOnEntityHit() : bool` - returns `true` by default, overridden by tridents (to be removed in a future major version in favour of cleaner BC-breaking methods)
|
||||
|
||||
### `pocketmine\event\block`
|
||||
- The following API methods have been added:
|
||||
- `public SignChangeEvent->isFrontFace() : bool` - returns `true` if the front face of the sign is being edited, `false` for the rear face
|
||||
|
||||
### `pocketmine\inventory\transaction`
|
||||
- `InventoryTransaction` no longer shuffles actions before executing a transaction.
|
||||
- This was intended to prevent dependency on weird client behaviour, but it is no longer necessary, as the order is now consistent since the introduction of the `ItemStackRequest` system.
|
||||
|
||||
### `pocketmine\item`
|
||||
- The following API classes have been added:
|
||||
- `FireworkRocket`
|
||||
- `FireworkRocketExplosion`
|
||||
- `FireworkRocketType` (enum)
|
||||
- `FireworkStar`
|
||||
- `Trident`
|
||||
- The following API methods have been added:
|
||||
- `VanillaItems::FIREWORK_ROCKET() : FireworkRocket`
|
||||
- `VanillaItems::FIREWORK_STAR() : FireworkStar`
|
||||
- `VanillaItems::TRIDENT() : Trident`
|
||||
|
||||
### `pocketmine\player`
|
||||
- The following API methods have signature changes:
|
||||
- `Player->openSignEditor()` now accepts an optional `bool $frontFace = true` parameter
|
||||
|
||||
### `pocketmine\world\sound`
|
||||
- The following API classes have been added:
|
||||
- `FireworkCrackleSound`
|
||||
- `FireworkExplosionSound`
|
||||
- `FireworkLargeExplosionSound`
|
||||
- `FireworkLaunchSound`
|
||||
- `TridentHitEntitySound`
|
||||
- `TridentHitBlockSound`
|
||||
- `TridentThrowSound`
|
||||
|
||||
## Internals
|
||||
- Many low-risk data handling areas have been switched to use `ext-encoding`, including:
|
||||
- Bedrock packets
|
||||
- Bedrock chunk serialization
|
||||
- `FastChunkSerializer` (used for transmitting chunks between threads)
|
||||
- GS4 Query
|
||||
- Auxiliary read-only data loading in the `pocketmine\data\bedrock` package
|
||||
|
||||
# 5.34.1
|
||||
Released 26th September 2025.
|
||||
|
||||
## Fixes
|
||||
- Player login JSON processing no longer bails out on unexpected extra properties. A warning will now be logged instead (@dktapps).
|
||||
- Fixed container drop issues when an ender crystal explosion causes another ender crystal nearby to explode (@dktapps, @kostamax27).
|
21
changelogs/5.35.md
Normal file
21
changelogs/5.35.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# 5.35.0
|
||||
Released 3rd October 2025.
|
||||
|
||||
This is a support release for Minecraft: Bedrock Edition 1.21.111.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## Interim releases
|
||||
If you're upgrading from 5.32.x directly to 5.35.0, please also read the following changelogs, as the interim releases contain important changes:
|
||||
- [5.33.0](https://github.com/pmmp/PocketMine-MP/blob/5.35.0/changelogs/5.33.md) - internals improvements, API improvements and new gameplay features
|
||||
- [5.34.0](https://github.com/pmmp/PocketMine-MP/blob/5.35.0/changelogs/5.34.md) - significant performance improvements, new gameplay features, new API additions and network changes
|
||||
|
||||
Please also note that an updated PHP binary is required due to changes introduced in 5.34.0 (initial `ext-encoding` integration).
|
||||
|
||||
## General
|
||||
- Added support for Minecraft: Bedrock Edition 1.21.111 (@dries-c).
|
||||
- Removed support for earlier versions.
|
@@ -34,15 +34,15 @@
|
||||
"composer-runtime-api": "^2.0",
|
||||
"adhocore/json-comment": "~1.2.0",
|
||||
"netresearch/jsonmapper": "~v5.0.0",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
|
||||
"pocketmine/bedrock-data": "~6.0.0+bedrock-1.21.100",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.15.0+bedrock-1.21.100",
|
||||
"pocketmine/bedrock-protocol": "~50.0.0+bedrock-1.21.100",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.2.0+bedrock-1.21.110",
|
||||
"pocketmine/bedrock-data": "~6.1.0+bedrock-1.21.111",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.16.0+bedrock-1.21.110",
|
||||
"pocketmine/bedrock-protocol": "~51.1.0+bedrock-1.21.111",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
"pocketmine/errorhandler": "^0.7.0",
|
||||
"pocketmine/locale-data": "~2.25.0",
|
||||
"pocketmine/locale-data": "~2.26.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/math": "~1.0.0",
|
||||
"pocketmine/nbt": "~1.2.0",
|
||||
|
62
composer.lock
generated
62
composer.lock
generated
@@ -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": "0d71d3fba23ba8c4734cac59b9e10129",
|
||||
"content-hash": "ad9a8e8069598ae5ec679f069461623f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@@ -178,16 +178,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-block-upgrade-schema",
|
||||
"version": "5.1.0",
|
||||
"version": "5.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
|
||||
"reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a"
|
||||
"reference": "5d7889c9a1cdf9e3cd814d2a104ad69b75116ec7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/2218512e4b91f5bfd09ef55f7a4c4b04e169e41a",
|
||||
"reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/5d7889c9a1cdf9e3cd814d2a104ad69b75116ec7",
|
||||
"reference": "5d7889c9a1cdf9e3cd814d2a104ad69b75116ec7",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@@ -198,22 +198,22 @@
|
||||
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
|
||||
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.1.0"
|
||||
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.2.0"
|
||||
},
|
||||
"time": "2025-02-11T17:41:44+00:00"
|
||||
"time": "2025-10-02T13:22:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-data",
|
||||
"version": "6.0.0+bedrock-1.21.100",
|
||||
"version": "6.1.0+bedrock-1.21.111",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockData.git",
|
||||
"reference": "edc0d829175e5e1e57c87001acfd03526c63fd34"
|
||||
"reference": "7484fe3c3d7949fd48cc520add4f7eeebc4ba4af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/edc0d829175e5e1e57c87001acfd03526c63fd34",
|
||||
"reference": "edc0d829175e5e1e57c87001acfd03526c63fd34",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/7484fe3c3d7949fd48cc520add4f7eeebc4ba4af",
|
||||
"reference": "7484fe3c3d7949fd48cc520add4f7eeebc4ba4af",
|
||||
"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/6.0.0+bedrock-1.21.100"
|
||||
"source": "https://github.com/pmmp/BedrockData/tree/6.1.0+bedrock-1.21.111"
|
||||
},
|
||||
"time": "2025-08-30T17:25:42+00:00"
|
||||
"time": "2025-10-02T15:28:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-item-upgrade-schema",
|
||||
"version": "1.15.0",
|
||||
"version": "1.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
|
||||
"reference": "09e0dbe9743f21a76b1fe04b2b4136785775f52b"
|
||||
"reference": "8c48ceaa72d390e89c4dbff9542aa4dfa734057d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/09e0dbe9743f21a76b1fe04b2b4136785775f52b",
|
||||
"reference": "09e0dbe9743f21a76b1fe04b2b4136785775f52b",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/8c48ceaa72d390e89c4dbff9542aa4dfa734057d",
|
||||
"reference": "8c48ceaa72d390e89c4dbff9542aa4dfa734057d",
|
||||
"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.15.0"
|
||||
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.16.0"
|
||||
},
|
||||
"time": "2025-08-06T15:08:48+00:00"
|
||||
"time": "2025-10-02T13:22:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-protocol",
|
||||
"version": "50.0.0+bedrock-1.21.100",
|
||||
"version": "51.1.0+bedrock-1.21.111",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||
"reference": "2d7aa27a5537ae593fb1c39158648ea462fef72a"
|
||||
"reference": "e380be227766ea58a874eb7d93de76f21c8ec04b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2d7aa27a5537ae593fb1c39158648ea462fef72a",
|
||||
"reference": "2d7aa27a5537ae593fb1c39158648ea462fef72a",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/e380be227766ea58a874eb7d93de76f21c8ec04b",
|
||||
"reference": "e380be227766ea58a874eb7d93de76f21c8ec04b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -297,9 +297,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/50.0.0+bedrock-1.21.100"
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/51.1.0+bedrock-1.21.111"
|
||||
},
|
||||
"time": "2025-09-20T23:09:19+00:00"
|
||||
"time": "2025-10-03T14:12:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/binaryutils",
|
||||
@@ -472,16 +472,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
"version": "2.25.1",
|
||||
"version": "2.26.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd"
|
||||
"reference": "f791369ae082fc5cdf0f2b0bd683e611ff7f90f6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd",
|
||||
"reference": "8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/f791369ae082fc5cdf0f2b0bd683e611ff7f90f6",
|
||||
"reference": "f791369ae082fc5cdf0f2b0bd683e611ff7f90f6",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@@ -489,9 +489,9 @@
|
||||
"description": "Language resources used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Language/issues",
|
||||
"source": "https://github.com/pmmp/Language/tree/2.25.1"
|
||||
"source": "https://github.com/pmmp/Language/tree/2.26.0"
|
||||
},
|
||||
"time": "2025-04-16T11:15:32+00:00"
|
||||
"time": "2025-10-07T17:26:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/log",
|
||||
|
@@ -872,7 +872,7 @@ class Server{
|
||||
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error2()));
|
||||
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error3()));
|
||||
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error4(Yml::SETTINGS_ENABLE_DEV_BUILDS)));
|
||||
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error5("https://github.com/pmmp/PocketMine-MP/releases")));
|
||||
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error5(VersionInfo::GITHUB_URL . "/releases")));
|
||||
$this->forceShutdownExit();
|
||||
|
||||
return;
|
||||
@@ -1093,7 +1093,23 @@ class Server{
|
||||
$this->configGroup->save();
|
||||
|
||||
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_defaultGameMode($this->getGamemode()->getTranslatableName())));
|
||||
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
|
||||
$highlight = TextFormat::AQUA;
|
||||
$reset = TextFormat::RESET;
|
||||
$github = VersionInfo::GITHUB_URL;
|
||||
$splash = "\n\n";
|
||||
foreach([
|
||||
KnownTranslationFactory::pocketmine_server_url_discord("{$highlight}https://discord.pmmp.io{$reset}"),
|
||||
KnownTranslationFactory::pocketmine_server_url_docs("{$highlight}https://doc.pmmp.io{$reset}"),
|
||||
KnownTranslationFactory::pocketmine_server_url_sourceCode("{$highlight}{$github}{$reset}"),
|
||||
KnownTranslationFactory::pocketmine_server_url_freePlugins("{$highlight}https://poggit.pmmp.io/plugins{$reset}"),
|
||||
KnownTranslationFactory::pocketmine_server_url_donations("{$highlight}https://patreon.com/pocketminemp{$reset}"),
|
||||
KnownTranslationFactory::pocketmine_server_url_translations("{$highlight}https://translate.pocketmine.net{$reset}"),
|
||||
KnownTranslationFactory::pocketmine_server_url_bugReporting("{$highlight}{$github}/issues{$reset}")
|
||||
] as $link){
|
||||
$splash .= "- " . $this->language->translate($link) . "\n";
|
||||
}
|
||||
$this->logger->info($splash);
|
||||
|
||||
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
|
||||
|
||||
$forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language);
|
||||
|
@@ -31,9 +31,10 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "5.33.3";
|
||||
public const BASE_VERSION = "5.35.1";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
public const GITHUB_URL = "https://github.com/pmmp/PocketMine-MP";
|
||||
|
||||
/**
|
||||
* PocketMine-MP-specific version ID for world data. Used to determine what fixes need to be applied to old world
|
||||
|
@@ -82,7 +82,7 @@ final class Cauldron extends Transparent{
|
||||
$this->fill(FillableCauldron::MAX_FILL_LEVEL, VanillaBlocks::LAVA_CAULDRON(), $item, VanillaItems::BUCKET(), $returnedItems);
|
||||
}elseif($item->getTypeId() === ItemTypeIds::POWDER_SNOW_BUCKET){
|
||||
//TODO: powder snow cauldron
|
||||
}elseif($item instanceof Potion || $item instanceof SplashPotion){ //TODO: lingering potion
|
||||
}elseif($item instanceof Potion || $item instanceof SplashPotion){
|
||||
if($item->getType() === PotionType::WATER){
|
||||
$this->fill(WaterCauldron::WATER_BOTTLE_FILL_AMOUNT, VanillaBlocks::WATER_CAULDRON(), $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
|
||||
}else{
|
||||
|
@@ -123,7 +123,7 @@ final class WaterCauldron extends FillableCauldron{
|
||||
$world->addSound($this->position->add(0.5, 0.5, 0.5), new CauldronAddDyeSound());
|
||||
|
||||
$item->pop();
|
||||
}elseif($item instanceof Potion || $item instanceof SplashPotion){ //TODO: lingering potion
|
||||
}elseif($item instanceof Potion || $item instanceof SplashPotion){
|
||||
if($item->getType() === PotionType::WATER){
|
||||
$this->setCustomWaterColor(null)->addFillLevels(self::WATER_BOTTLE_FILL_AMOUNT, $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
|
||||
}else{
|
||||
|
@@ -93,6 +93,7 @@ class Campfire extends Spawnable implements Container{
|
||||
$listeners = $this->inventory->getListeners()->toArray();
|
||||
$this->inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
|
||||
|
||||
$baseErrorContext = "Campfire ($this->position)";
|
||||
foreach([
|
||||
[0, self::TAG_FIRST_INPUT_ITEM, self::TAG_FIRST_COOKING_TIME],
|
||||
[1, self::TAG_SECOND_INPUT_ITEM, self::TAG_SECOND_COOKING_TIME],
|
||||
@@ -100,7 +101,7 @@ class Campfire extends Spawnable implements Container{
|
||||
[3, self::TAG_FOURTH_INPUT_ITEM, self::TAG_FOURTH_COOKING_TIME],
|
||||
] as [$slot, $itemTag, $cookingTimeTag]){
|
||||
if(($tag = $nbt->getTag($itemTag)) instanceof CompoundTag){
|
||||
$items[$slot] = Item::nbtDeserialize($tag);
|
||||
$items[$slot] = Item::safeNbtDeserialize($tag, "$baseErrorContext slot $slot");
|
||||
}
|
||||
if(($tag = $nbt->getTag($cookingTimeTag)) instanceof IntTag){
|
||||
$this->cookingTimes[$slot] = $tag->getValue();
|
||||
|
@@ -76,7 +76,6 @@ final class Cauldron extends Spawnable{
|
||||
default => throw new AssumptionFailedError("Unexpected potion item type")
|
||||
});
|
||||
|
||||
//TODO: lingering potion
|
||||
$type = $this->potionItem instanceof Potion || $this->potionItem instanceof SplashPotion ? $this->potionItem->getType() : null;
|
||||
$nbt->setShort(self::TAG_POTION_ID, $type === null ? self::POTION_ID_NONE : PotionTypeIdMap::getInstance()->toId($type));
|
||||
|
||||
@@ -96,7 +95,7 @@ final class Cauldron extends Spawnable{
|
||||
$this->potionItem = match($containerType){
|
||||
self::POTION_CONTAINER_TYPE_NORMAL => VanillaItems::POTION()->setType($potionType),
|
||||
self::POTION_CONTAINER_TYPE_SPLASH => VanillaItems::SPLASH_POTION()->setType($potionType),
|
||||
self::POTION_CONTAINER_TYPE_LINGERING => throw new SavedDataLoadingException("Not implemented"),
|
||||
self::POTION_CONTAINER_TYPE_LINGERING => VanillaItems::LINGERING_POTION()->setType($potionType),
|
||||
default => throw new SavedDataLoadingException("Invalid potion container type ID $containerType")
|
||||
};
|
||||
}else{
|
||||
@@ -115,7 +114,6 @@ final class Cauldron extends Spawnable{
|
||||
default => throw new AssumptionFailedError("Unexpected potion item type")
|
||||
});
|
||||
|
||||
//TODO: lingering potion
|
||||
$type = $this->potionItem instanceof Potion || $this->potionItem instanceof SplashPotion ? $this->potionItem->getType() : null;
|
||||
$nbt->setShort(self::TAG_POTION_ID, $type === null ? self::POTION_ID_NONE : PotionTypeIdMap::getInstance()->toId($type));
|
||||
|
||||
|
@@ -26,7 +26,6 @@ namespace pocketmine\block\tile;
|
||||
use pocketmine\block\utils\ChiseledBookshelfSlot;
|
||||
use pocketmine\data\bedrock\item\SavedItemData;
|
||||
use pocketmine\data\bedrock\item\SavedItemStackData;
|
||||
use pocketmine\data\SavedDataLoadingException;
|
||||
use pocketmine\inventory\SimpleInventory;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\Vector3;
|
||||
@@ -99,18 +98,13 @@ class ChiseledBookshelf extends Tile implements Container{
|
||||
$inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
|
||||
|
||||
$newContents = [];
|
||||
$errorLogContext = "ChiseledBookshelf ($this->position)";
|
||||
foreach($inventoryTag as $slot => $itemNBT){
|
||||
try{
|
||||
$count = $itemNBT->getByte(SavedItemStackData::TAG_COUNT);
|
||||
if($count === 0){
|
||||
continue;
|
||||
}
|
||||
$newContents[$slot] = Item::nbtDeserialize($itemNBT);
|
||||
}catch(SavedDataLoadingException $e){
|
||||
//TODO: not the best solution
|
||||
\GlobalLogger::get()->logException($e);
|
||||
$count = $itemNBT->getByte(SavedItemStackData::TAG_COUNT);
|
||||
if($count === 0){
|
||||
continue;
|
||||
}
|
||||
$newContents[$slot] = Item::safeNbtDeserialize($itemNBT, "$errorLogContext slot $slot");
|
||||
}
|
||||
$inventory->setContents($newContents);
|
||||
|
||||
|
@@ -24,7 +24,6 @@ declare(strict_types=1);
|
||||
namespace pocketmine\block\tile;
|
||||
|
||||
use pocketmine\data\bedrock\item\SavedItemStackData;
|
||||
use pocketmine\data\SavedDataLoadingException;
|
||||
use pocketmine\inventory\Inventory;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\nbt\NBT;
|
||||
@@ -56,14 +55,10 @@ trait ContainerTrait{
|
||||
$inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
|
||||
|
||||
$newContents = [];
|
||||
$errorLogContext = "Container (" . $this->getPosition() . ")";
|
||||
foreach($inventoryTag as $itemNBT){
|
||||
try{
|
||||
$newContents[$itemNBT->getByte(SavedItemStackData::TAG_SLOT)] = Item::nbtDeserialize($itemNBT);
|
||||
}catch(SavedDataLoadingException $e){
|
||||
//TODO: not the best solution
|
||||
\GlobalLogger::get()->logException($e);
|
||||
continue;
|
||||
}
|
||||
$slotId = $itemNBT->getByte(SavedItemStackData::TAG_SLOT);
|
||||
$newContents[$slotId] = Item::safeNbtDeserialize($itemNBT, "$errorLogContext slot $slotId");
|
||||
}
|
||||
$inventory->setContents($newContents);
|
||||
|
||||
|
@@ -51,7 +51,7 @@ class ItemFrame extends Spawnable{
|
||||
|
||||
public function readSaveData(CompoundTag $nbt) : void{
|
||||
if(($itemTag = $nbt->getCompoundTag(self::TAG_ITEM)) !== null){
|
||||
$this->item = Item::nbtDeserialize($itemTag);
|
||||
$this->item = Item::safeNbtDeserialize($itemTag, "ItemFrame ($this->position) framed item");
|
||||
}
|
||||
if($nbt->getTag(self::TAG_ITEM_ROTATION) instanceof FloatTag){
|
||||
$this->itemRotation = (int) ($nbt->getFloat(self::TAG_ITEM_ROTATION, $this->itemRotation * 45) / 45);
|
||||
|
@@ -44,7 +44,7 @@ class Jukebox extends Spawnable{
|
||||
|
||||
public function readSaveData(CompoundTag $nbt) : void{
|
||||
if(($tag = $nbt->getCompoundTag(self::TAG_RECORD)) !== null){
|
||||
$record = Item::nbtDeserialize($tag);
|
||||
$record = Item::safeNbtDeserialize($tag, "Jukebox ($this->position) record");
|
||||
if($record instanceof Record){
|
||||
$this->record = $record;
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ class Lectern extends Spawnable{
|
||||
public function readSaveData(CompoundTag $nbt) : void{
|
||||
$this->viewedPage = $nbt->getInt(self::TAG_PAGE, 0);
|
||||
if(($itemTag = $nbt->getCompoundTag(self::TAG_BOOK)) !== null){
|
||||
$book = Item::nbtDeserialize($itemTag);
|
||||
$book = Item::safeNbtDeserialize($itemTag, "Lectern ($this->position) book");
|
||||
if($book instanceof WritableBookBase && !$book->isNull()){
|
||||
$this->book = $book;
|
||||
}
|
||||
|
@@ -109,6 +109,7 @@ final class BlockStateNames{
|
||||
public const PILLAR_AXIS = "pillar_axis";
|
||||
public const PORTAL_AXIS = "portal_axis";
|
||||
public const POWERED_BIT = "powered_bit";
|
||||
public const POWERED_SHELF_TYPE = "powered_shelf_type";
|
||||
public const PROPAGULE_STAGE = "propagule_stage";
|
||||
public const RAIL_DATA_BIT = "rail_data_bit";
|
||||
public const RAIL_DIRECTION = "rail_direction";
|
||||
|
@@ -42,6 +42,7 @@ final class BlockTypeNames{
|
||||
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_SHELF = "minecraft:acacia_shelf";
|
||||
public const ACACIA_SLAB = "minecraft:acacia_slab";
|
||||
public const ACACIA_STAIRS = "minecraft:acacia_stairs";
|
||||
public const ACACIA_STANDING_SIGN = "minecraft:acacia_standing_sign";
|
||||
@@ -80,6 +81,7 @@ final class BlockTypeNames{
|
||||
public const BAMBOO_PLANKS = "minecraft:bamboo_planks";
|
||||
public const BAMBOO_PRESSURE_PLATE = "minecraft:bamboo_pressure_plate";
|
||||
public const BAMBOO_SAPLING = "minecraft:bamboo_sapling";
|
||||
public const BAMBOO_SHELF = "minecraft:bamboo_shelf";
|
||||
public const BAMBOO_SLAB = "minecraft:bamboo_slab";
|
||||
public const BAMBOO_STAIRS = "minecraft:bamboo_stairs";
|
||||
public const BAMBOO_STANDING_SIGN = "minecraft:bamboo_standing_sign";
|
||||
@@ -107,6 +109,7 @@ final class BlockTypeNames{
|
||||
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_SHELF = "minecraft:birch_shelf";
|
||||
public const BIRCH_SLAB = "minecraft:birch_slab";
|
||||
public const BIRCH_STAIRS = "minecraft:birch_stairs";
|
||||
public const BIRCH_STANDING_SIGN = "minecraft:birch_standing_sign";
|
||||
@@ -192,7 +195,6 @@ final class BlockTypeNames{
|
||||
public const CAVE_VINES = "minecraft:cave_vines";
|
||||
public const CAVE_VINES_BODY_WITH_BERRIES = "minecraft:cave_vines_body_with_berries";
|
||||
public const CAVE_VINES_HEAD_WITH_BERRIES = "minecraft:cave_vines_head_with_berries";
|
||||
public const CHAIN = "minecraft:chain";
|
||||
public const CHAIN_COMMAND_BLOCK = "minecraft:chain_command_block";
|
||||
public const CHALKBOARD = "minecraft:chalkboard";
|
||||
public const CHEMICAL_HEAT = "minecraft:chemical_heat";
|
||||
@@ -207,6 +209,7 @@ final class BlockTypeNames{
|
||||
public const CHERRY_PLANKS = "minecraft:cherry_planks";
|
||||
public const CHERRY_PRESSURE_PLATE = "minecraft:cherry_pressure_plate";
|
||||
public const CHERRY_SAPLING = "minecraft:cherry_sapling";
|
||||
public const CHERRY_SHELF = "minecraft:cherry_shelf";
|
||||
public const CHERRY_SLAB = "minecraft:cherry_slab";
|
||||
public const CHERRY_STAIRS = "minecraft:cherry_stairs";
|
||||
public const CHERRY_STANDING_SIGN = "minecraft:cherry_standing_sign";
|
||||
@@ -253,12 +256,17 @@ final class BlockTypeNames{
|
||||
public const COMPOSTER = "minecraft:composter";
|
||||
public const COMPOUND_CREATOR = "minecraft:compound_creator";
|
||||
public const CONDUIT = "minecraft:conduit";
|
||||
public const COPPER_BARS = "minecraft:copper_bars";
|
||||
public const COPPER_BLOCK = "minecraft:copper_block";
|
||||
public const COPPER_BULB = "minecraft:copper_bulb";
|
||||
public const COPPER_CHAIN = "minecraft:copper_chain";
|
||||
public const COPPER_CHEST = "minecraft:copper_chest";
|
||||
public const COPPER_DOOR = "minecraft:copper_door";
|
||||
public const COPPER_GOLEM_STATUE = "minecraft:copper_golem_statue";
|
||||
public const COPPER_GRATE = "minecraft:copper_grate";
|
||||
public const COPPER_LANTERN = "minecraft:copper_lantern";
|
||||
public const COPPER_ORE = "minecraft:copper_ore";
|
||||
public const COPPER_TORCH = "minecraft:copper_torch";
|
||||
public const COPPER_TRAPDOOR = "minecraft:copper_trapdoor";
|
||||
public const CORNFLOWER = "minecraft:cornflower";
|
||||
public const CRACKED_DEEPSLATE_BRICKS = "minecraft:cracked_deepslate_bricks";
|
||||
@@ -282,6 +290,7 @@ final class BlockTypeNames{
|
||||
public const CRIMSON_PLANKS = "minecraft:crimson_planks";
|
||||
public const CRIMSON_PRESSURE_PLATE = "minecraft:crimson_pressure_plate";
|
||||
public const CRIMSON_ROOTS = "minecraft:crimson_roots";
|
||||
public const CRIMSON_SHELF = "minecraft:crimson_shelf";
|
||||
public const CRIMSON_SLAB = "minecraft:crimson_slab";
|
||||
public const CRIMSON_STAIRS = "minecraft:crimson_stairs";
|
||||
public const CRIMSON_STANDING_SIGN = "minecraft:crimson_standing_sign";
|
||||
@@ -322,6 +331,7 @@ final class BlockTypeNames{
|
||||
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_SHELF = "minecraft:dark_oak_shelf";
|
||||
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";
|
||||
@@ -533,15 +543,20 @@ final class BlockTypeNames{
|
||||
public const ENDER_CHEST = "minecraft:ender_chest";
|
||||
public const EXPOSED_CHISELED_COPPER = "minecraft:exposed_chiseled_copper";
|
||||
public const EXPOSED_COPPER = "minecraft:exposed_copper";
|
||||
public const EXPOSED_COPPER_BARS = "minecraft:exposed_copper_bars";
|
||||
public const EXPOSED_COPPER_BULB = "minecraft:exposed_copper_bulb";
|
||||
public const EXPOSED_COPPER_CHAIN = "minecraft:exposed_copper_chain";
|
||||
public const EXPOSED_COPPER_CHEST = "minecraft:exposed_copper_chest";
|
||||
public const EXPOSED_COPPER_DOOR = "minecraft:exposed_copper_door";
|
||||
public const EXPOSED_COPPER_GOLEM_STATUE = "minecraft:exposed_copper_golem_statue";
|
||||
public const EXPOSED_COPPER_GRATE = "minecraft:exposed_copper_grate";
|
||||
public const EXPOSED_COPPER_LANTERN = "minecraft:exposed_copper_lantern";
|
||||
public const EXPOSED_COPPER_TRAPDOOR = "minecraft:exposed_copper_trapdoor";
|
||||
public const EXPOSED_CUT_COPPER = "minecraft:exposed_cut_copper";
|
||||
public const EXPOSED_CUT_COPPER_SLAB = "minecraft:exposed_cut_copper_slab";
|
||||
public const EXPOSED_CUT_COPPER_STAIRS = "minecraft:exposed_cut_copper_stairs";
|
||||
public const EXPOSED_DOUBLE_CUT_COPPER_SLAB = "minecraft:exposed_double_cut_copper_slab";
|
||||
public const EXPOSED_LIGHTNING_ROD = "minecraft:exposed_lightning_rod";
|
||||
public const FARMLAND = "minecraft:farmland";
|
||||
public const FENCE_GATE = "minecraft:fence_gate";
|
||||
public const FERN = "minecraft:fern";
|
||||
@@ -660,6 +675,7 @@ final class BlockTypeNames{
|
||||
public const INVISIBLE_BEDROCK = "minecraft:invisible_bedrock";
|
||||
public const IRON_BARS = "minecraft:iron_bars";
|
||||
public const IRON_BLOCK = "minecraft:iron_block";
|
||||
public const IRON_CHAIN = "minecraft:iron_chain";
|
||||
public const IRON_DOOR = "minecraft:iron_door";
|
||||
public const IRON_ORE = "minecraft:iron_ore";
|
||||
public const IRON_TRAPDOOR = "minecraft:iron_trapdoor";
|
||||
@@ -676,6 +692,7 @@ final class BlockTypeNames{
|
||||
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_SHELF = "minecraft:jungle_shelf";
|
||||
public const JUNGLE_SLAB = "minecraft:jungle_slab";
|
||||
public const JUNGLE_STAIRS = "minecraft:jungle_stairs";
|
||||
public const JUNGLE_STANDING_SIGN = "minecraft:jungle_standing_sign";
|
||||
@@ -779,6 +796,7 @@ final class BlockTypeNames{
|
||||
public const MANGROVE_PRESSURE_PLATE = "minecraft:mangrove_pressure_plate";
|
||||
public const MANGROVE_PROPAGULE = "minecraft:mangrove_propagule";
|
||||
public const MANGROVE_ROOTS = "minecraft:mangrove_roots";
|
||||
public const MANGROVE_SHELF = "minecraft:mangrove_shelf";
|
||||
public const MANGROVE_SLAB = "minecraft:mangrove_slab";
|
||||
public const MANGROVE_STAIRS = "minecraft:mangrove_stairs";
|
||||
public const MANGROVE_STANDING_SIGN = "minecraft:mangrove_standing_sign";
|
||||
@@ -836,6 +854,7 @@ final class BlockTypeNames{
|
||||
public const OAK_LOG = "minecraft:oak_log";
|
||||
public const OAK_PLANKS = "minecraft:oak_planks";
|
||||
public const OAK_SAPLING = "minecraft:oak_sapling";
|
||||
public const OAK_SHELF = "minecraft:oak_shelf";
|
||||
public const OAK_SLAB = "minecraft:oak_slab";
|
||||
public const OAK_STAIRS = "minecraft:oak_stairs";
|
||||
public const OAK_WOOD = "minecraft:oak_wood";
|
||||
@@ -858,15 +877,20 @@ final class BlockTypeNames{
|
||||
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_BARS = "minecraft:oxidized_copper_bars";
|
||||
public const OXIDIZED_COPPER_BULB = "minecraft:oxidized_copper_bulb";
|
||||
public const OXIDIZED_COPPER_CHAIN = "minecraft:oxidized_copper_chain";
|
||||
public const OXIDIZED_COPPER_CHEST = "minecraft:oxidized_copper_chest";
|
||||
public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door";
|
||||
public const OXIDIZED_COPPER_GOLEM_STATUE = "minecraft:oxidized_copper_golem_statue";
|
||||
public const OXIDIZED_COPPER_GRATE = "minecraft:oxidized_copper_grate";
|
||||
public const OXIDIZED_COPPER_LANTERN = "minecraft:oxidized_copper_lantern";
|
||||
public const OXIDIZED_COPPER_TRAPDOOR = "minecraft:oxidized_copper_trapdoor";
|
||||
public const OXIDIZED_CUT_COPPER = "minecraft:oxidized_cut_copper";
|
||||
public const OXIDIZED_CUT_COPPER_SLAB = "minecraft:oxidized_cut_copper_slab";
|
||||
public const OXIDIZED_CUT_COPPER_STAIRS = "minecraft:oxidized_cut_copper_stairs";
|
||||
public const OXIDIZED_DOUBLE_CUT_COPPER_SLAB = "minecraft:oxidized_double_cut_copper_slab";
|
||||
public const OXIDIZED_LIGHTNING_ROD = "minecraft:oxidized_lightning_rod";
|
||||
public const PACKED_ICE = "minecraft:packed_ice";
|
||||
public const PACKED_MUD = "minecraft:packed_mud";
|
||||
public const PALE_HANGING_MOSS = "minecraft:pale_hanging_moss";
|
||||
@@ -883,6 +907,7 @@ final class BlockTypeNames{
|
||||
public const PALE_OAK_PLANKS = "minecraft:pale_oak_planks";
|
||||
public const PALE_OAK_PRESSURE_PLATE = "minecraft:pale_oak_pressure_plate";
|
||||
public const PALE_OAK_SAPLING = "minecraft:pale_oak_sapling";
|
||||
public const PALE_OAK_SHELF = "minecraft:pale_oak_shelf";
|
||||
public const PALE_OAK_SLAB = "minecraft:pale_oak_slab";
|
||||
public const PALE_OAK_STAIRS = "minecraft:pale_oak_stairs";
|
||||
public const PALE_OAK_STANDING_SIGN = "minecraft:pale_oak_standing_sign";
|
||||
@@ -1099,6 +1124,7 @@ final class BlockTypeNames{
|
||||
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_SHELF = "minecraft:spruce_shelf";
|
||||
public const SPRUCE_SLAB = "minecraft:spruce_slab";
|
||||
public const SPRUCE_STAIRS = "minecraft:spruce_stairs";
|
||||
public const SPRUCE_STANDING_SIGN = "minecraft:spruce_standing_sign";
|
||||
@@ -1202,6 +1228,7 @@ final class BlockTypeNames{
|
||||
public const WARPED_PLANKS = "minecraft:warped_planks";
|
||||
public const WARPED_PRESSURE_PLATE = "minecraft:warped_pressure_plate";
|
||||
public const WARPED_ROOTS = "minecraft:warped_roots";
|
||||
public const WARPED_SHELF = "minecraft:warped_shelf";
|
||||
public const WARPED_SLAB = "minecraft:warped_slab";
|
||||
public const WARPED_STAIRS = "minecraft:warped_stairs";
|
||||
public const WARPED_STANDING_SIGN = "minecraft:warped_standing_sign";
|
||||
@@ -1213,10 +1240,14 @@ final class BlockTypeNames{
|
||||
public const WATERLILY = "minecraft:waterlily";
|
||||
public const WAXED_CHISELED_COPPER = "minecraft:waxed_chiseled_copper";
|
||||
public const WAXED_COPPER = "minecraft:waxed_copper";
|
||||
public const WAXED_COPPER_BARS = "minecraft:waxed_copper_bars";
|
||||
public const WAXED_COPPER_BULB = "minecraft:waxed_copper_bulb";
|
||||
public const WAXED_COPPER_CHAIN = "minecraft:waxed_copper_chain";
|
||||
public const WAXED_COPPER_CHEST = "minecraft:waxed_copper_chest";
|
||||
public const WAXED_COPPER_DOOR = "minecraft:waxed_copper_door";
|
||||
public const WAXED_COPPER_GOLEM_STATUE = "minecraft:waxed_copper_golem_statue";
|
||||
public const WAXED_COPPER_GRATE = "minecraft:waxed_copper_grate";
|
||||
public const WAXED_COPPER_LANTERN = "minecraft:waxed_copper_lantern";
|
||||
public const WAXED_COPPER_TRAPDOOR = "minecraft:waxed_copper_trapdoor";
|
||||
public const WAXED_CUT_COPPER = "minecraft:waxed_cut_copper";
|
||||
public const WAXED_CUT_COPPER_SLAB = "minecraft:waxed_cut_copper_slab";
|
||||
@@ -1224,48 +1255,69 @@ final class BlockTypeNames{
|
||||
public const WAXED_DOUBLE_CUT_COPPER_SLAB = "minecraft:waxed_double_cut_copper_slab";
|
||||
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_BARS = "minecraft:waxed_exposed_copper_bars";
|
||||
public const WAXED_EXPOSED_COPPER_BULB = "minecraft:waxed_exposed_copper_bulb";
|
||||
public const WAXED_EXPOSED_COPPER_CHAIN = "minecraft:waxed_exposed_copper_chain";
|
||||
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_GOLEM_STATUE = "minecraft:waxed_exposed_copper_golem_statue";
|
||||
public const WAXED_EXPOSED_COPPER_GRATE = "minecraft:waxed_exposed_copper_grate";
|
||||
public const WAXED_EXPOSED_COPPER_LANTERN = "minecraft:waxed_exposed_copper_lantern";
|
||||
public const WAXED_EXPOSED_COPPER_TRAPDOOR = "minecraft:waxed_exposed_copper_trapdoor";
|
||||
public const WAXED_EXPOSED_CUT_COPPER = "minecraft:waxed_exposed_cut_copper";
|
||||
public const WAXED_EXPOSED_CUT_COPPER_SLAB = "minecraft:waxed_exposed_cut_copper_slab";
|
||||
public const WAXED_EXPOSED_CUT_COPPER_STAIRS = "minecraft:waxed_exposed_cut_copper_stairs";
|
||||
public const WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB = "minecraft:waxed_exposed_double_cut_copper_slab";
|
||||
public const WAXED_EXPOSED_LIGHTNING_ROD = "minecraft:waxed_exposed_lightning_rod";
|
||||
public const WAXED_LIGHTNING_ROD = "minecraft:waxed_lightning_rod";
|
||||
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_BARS = "minecraft:waxed_oxidized_copper_bars";
|
||||
public const WAXED_OXIDIZED_COPPER_BULB = "minecraft:waxed_oxidized_copper_bulb";
|
||||
public const WAXED_OXIDIZED_COPPER_CHAIN = "minecraft:waxed_oxidized_copper_chain";
|
||||
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_GOLEM_STATUE = "minecraft:waxed_oxidized_copper_golem_statue";
|
||||
public const WAXED_OXIDIZED_COPPER_GRATE = "minecraft:waxed_oxidized_copper_grate";
|
||||
public const WAXED_OXIDIZED_COPPER_LANTERN = "minecraft:waxed_oxidized_copper_lantern";
|
||||
public const WAXED_OXIDIZED_COPPER_TRAPDOOR = "minecraft:waxed_oxidized_copper_trapdoor";
|
||||
public const WAXED_OXIDIZED_CUT_COPPER = "minecraft:waxed_oxidized_cut_copper";
|
||||
public const WAXED_OXIDIZED_CUT_COPPER_SLAB = "minecraft:waxed_oxidized_cut_copper_slab";
|
||||
public const WAXED_OXIDIZED_CUT_COPPER_STAIRS = "minecraft:waxed_oxidized_cut_copper_stairs";
|
||||
public const WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB = "minecraft:waxed_oxidized_double_cut_copper_slab";
|
||||
public const WAXED_OXIDIZED_LIGHTNING_ROD = "minecraft:waxed_oxidized_lightning_rod";
|
||||
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_BARS = "minecraft:waxed_weathered_copper_bars";
|
||||
public const WAXED_WEATHERED_COPPER_BULB = "minecraft:waxed_weathered_copper_bulb";
|
||||
public const WAXED_WEATHERED_COPPER_CHAIN = "minecraft:waxed_weathered_copper_chain";
|
||||
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_GOLEM_STATUE = "minecraft:waxed_weathered_copper_golem_statue";
|
||||
public const WAXED_WEATHERED_COPPER_GRATE = "minecraft:waxed_weathered_copper_grate";
|
||||
public const WAXED_WEATHERED_COPPER_LANTERN = "minecraft:waxed_weathered_copper_lantern";
|
||||
public const WAXED_WEATHERED_COPPER_TRAPDOOR = "minecraft:waxed_weathered_copper_trapdoor";
|
||||
public const WAXED_WEATHERED_CUT_COPPER = "minecraft:waxed_weathered_cut_copper";
|
||||
public const WAXED_WEATHERED_CUT_COPPER_SLAB = "minecraft:waxed_weathered_cut_copper_slab";
|
||||
public const WAXED_WEATHERED_CUT_COPPER_STAIRS = "minecraft:waxed_weathered_cut_copper_stairs";
|
||||
public const WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB = "minecraft:waxed_weathered_double_cut_copper_slab";
|
||||
public const WAXED_WEATHERED_LIGHTNING_ROD = "minecraft:waxed_weathered_lightning_rod";
|
||||
public const WEATHERED_CHISELED_COPPER = "minecraft:weathered_chiseled_copper";
|
||||
public const WEATHERED_COPPER = "minecraft:weathered_copper";
|
||||
public const WEATHERED_COPPER_BARS = "minecraft:weathered_copper_bars";
|
||||
public const WEATHERED_COPPER_BULB = "minecraft:weathered_copper_bulb";
|
||||
public const WEATHERED_COPPER_CHAIN = "minecraft:weathered_copper_chain";
|
||||
public const WEATHERED_COPPER_CHEST = "minecraft:weathered_copper_chest";
|
||||
public const WEATHERED_COPPER_DOOR = "minecraft:weathered_copper_door";
|
||||
public const WEATHERED_COPPER_GOLEM_STATUE = "minecraft:weathered_copper_golem_statue";
|
||||
public const WEATHERED_COPPER_GRATE = "minecraft:weathered_copper_grate";
|
||||
public const WEATHERED_COPPER_LANTERN = "minecraft:weathered_copper_lantern";
|
||||
public const WEATHERED_COPPER_TRAPDOOR = "minecraft:weathered_copper_trapdoor";
|
||||
public const WEATHERED_CUT_COPPER = "minecraft:weathered_cut_copper";
|
||||
public const WEATHERED_CUT_COPPER_SLAB = "minecraft:weathered_cut_copper_slab";
|
||||
public const WEATHERED_CUT_COPPER_STAIRS = "minecraft:weathered_cut_copper_stairs";
|
||||
public const WEATHERED_DOUBLE_CUT_COPPER_SLAB = "minecraft:weathered_double_cut_copper_slab";
|
||||
public const WEATHERED_LIGHTNING_ROD = "minecraft:weathered_lightning_rod";
|
||||
public const WEB = "minecraft:web";
|
||||
public const WEEPING_VINES = "minecraft:weeping_vines";
|
||||
public const WET_SPONGE = "minecraft:wet_sponge";
|
||||
|
@@ -1264,7 +1264,7 @@ final class VanillaBlockMappings{
|
||||
$reg->mapModel(Model::create(Blocks::CARVED_PUMPKIN(), Ids::CARVED_PUMPKIN)->properties([
|
||||
$commonProperties->horizontalFacingCardinal
|
||||
]));
|
||||
$reg->mapModel(Model::create(Blocks::CHAIN(), Ids::CHAIN)->properties([$commonProperties->pillarAxis]));
|
||||
$reg->mapModel(Model::create(Blocks::CHAIN(), Ids::IRON_CHAIN)->properties([$commonProperties->pillarAxis]));
|
||||
$reg->mapModel(Model::create(Blocks::CHISELED_BOOKSHELF(), Ids::CHISELED_BOOKSHELF)->properties([
|
||||
$commonProperties->horizontalFacingSWNE,
|
||||
new ValueSetFromIntProperty(
|
||||
@@ -1355,7 +1355,10 @@ final class VanillaBlockMappings{
|
||||
new ValueFromStringProperty(StateNames::LEVER_DIRECTION, ValueMappings::getInstance()->leverFacing, fn(Lever $b) => $b->getFacing(), fn(Lever $b, LeverFacing $v) => $b->setFacing($v)),
|
||||
new BoolProperty(StateNames::OPEN_BIT, fn(Lever $b) => $b->isActivated(), fn(Lever $b, bool $v) => $b->setActivated($v)),
|
||||
]));
|
||||
$reg->mapModel(Model::create(Blocks::LIGHTNING_ROD(), Ids::LIGHTNING_ROD)->properties([$commonProperties->anyFacingClassic]));
|
||||
$reg->mapModel(Model::create(Blocks::LIGHTNING_ROD(), Ids::LIGHTNING_ROD)->properties([
|
||||
$commonProperties->anyFacingClassic,
|
||||
new DummyProperty(StateNames::POWERED_BIT, false) //TODO
|
||||
]));
|
||||
$reg->mapModel(Model::create(Blocks::LIT_PUMPKIN(), Ids::LIT_PUMPKIN)->properties([$commonProperties->horizontalFacingCardinal]));
|
||||
$reg->mapModel(Model::create(Blocks::LOOM(), Ids::LOOM)->properties([$commonProperties->horizontalFacingSWNE]));
|
||||
|
||||
|
@@ -141,7 +141,6 @@ final class ItemSerializerDeserializerRegistrar{
|
||||
$this->map1to1Block(Ids::CAKE, Blocks::CAKE());
|
||||
$this->map1to1Block(Ids::CAMPFIRE, Blocks::CAMPFIRE());
|
||||
$this->map1to1Block(Ids::CAULDRON, Blocks::CAULDRON());
|
||||
$this->map1to1Block(Ids::CHAIN, Blocks::CHAIN());
|
||||
$this->map1to1Block(Ids::CHERRY_DOOR, Blocks::CHERRY_DOOR());
|
||||
$this->map1to1Block(Ids::COMPARATOR, Blocks::REDSTONE_COMPARATOR());
|
||||
$this->map1to1Block(Ids::CRIMSON_DOOR, Blocks::CRIMSON_DOOR());
|
||||
@@ -519,6 +518,14 @@ final class ItemSerializerDeserializerRegistrar{
|
||||
},
|
||||
fn(GoatHorn $item) => GoatHornTypeIdMap::getInstance()->toId($item->getHornType())
|
||||
);
|
||||
$this->map1to1ItemWithMeta(
|
||||
Ids::LINGERING_POTION,
|
||||
Items::LINGERING_POTION(),
|
||||
function(SplashPotion $item, int $meta) : void{
|
||||
$item->setType(PotionTypeIdMap::getInstance()->fromId($meta) ?? throw new ItemTypeDeserializeException("Unknown potion type ID $meta"));
|
||||
},
|
||||
fn(SplashPotion $item) => PotionTypeIdMap::getInstance()->toId($item->getType())
|
||||
);
|
||||
$this->map1to1ItemWithMeta(
|
||||
Ids::MEDICINE,
|
||||
Items::MEDICINE(),
|
||||
|
@@ -112,7 +112,6 @@ final class ItemTypeNames{
|
||||
public const CAT_SPAWN_EGG = "minecraft:cat_spawn_egg";
|
||||
public const CAULDRON = "minecraft:cauldron";
|
||||
public const CAVE_SPIDER_SPAWN_EGG = "minecraft:cave_spider_spawn_egg";
|
||||
public const CHAIN = "minecraft:chain";
|
||||
public const CHAINMAIL_BOOTS = "minecraft:chainmail_boots";
|
||||
public const CHAINMAIL_CHESTPLATE = "minecraft:chainmail_chestplate";
|
||||
public const CHAINMAIL_HELMET = "minecraft:chainmail_helmet";
|
||||
@@ -160,6 +159,7 @@ final class ItemTypeNames{
|
||||
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_HORSE_ARMOR = "minecraft:copper_horse_armor";
|
||||
public const COPPER_INGOT = "minecraft:copper_ingot";
|
||||
public const COPPER_LEGGINGS = "minecraft:copper_leggings";
|
||||
public const COPPER_NUGGET = "minecraft:copper_nugget";
|
||||
|
@@ -32,6 +32,7 @@ use pocketmine\data\bedrock\PotionTypeIdMap;
|
||||
use pocketmine\data\bedrock\PotionTypeIds;
|
||||
use pocketmine\data\SavedDataLoadingException;
|
||||
use pocketmine\entity\EntityDataHelper as Helper;
|
||||
use pocketmine\entity\object\AreaEffectCloud;
|
||||
use pocketmine\entity\object\EndCrystal;
|
||||
use pocketmine\entity\object\ExperienceOrb;
|
||||
use pocketmine\entity\object\FallingBlock;
|
||||
@@ -87,6 +88,10 @@ final class EntityFactory{
|
||||
//define legacy save IDs first - use them for saving for maximum compatibility with Minecraft PC
|
||||
//TODO: index them by version to allow proper multi-save compatibility
|
||||
|
||||
$this->register(AreaEffectCloud::class, function(World $world, CompoundTag $nbt) : AreaEffectCloud{
|
||||
return new AreaEffectCloud(Helper::parseLocation($nbt, $world), $nbt);
|
||||
}, ['AreaEffectCloud', 'minecraft:area_effect_cloud']);
|
||||
|
||||
$this->register(Arrow::class, function(World $world, CompoundTag $nbt) : Arrow{
|
||||
return new Arrow(Helper::parseLocation($nbt, $world), null, $nbt->getByte(Arrow::TAG_CRIT, 0) === 1, $nbt);
|
||||
}, ['Arrow', 'minecraft:arrow']);
|
||||
|
@@ -309,9 +309,11 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
if($slot >= 0 && $slot < 9){ //Hotbar
|
||||
//Old hotbar saving stuff, ignore it
|
||||
}elseif($slot >= 100 && $slot < 104){ //Armor
|
||||
$armorInventoryItems[$slot - 100] = Item::nbtDeserialize($item);
|
||||
$armorSlot = $slot - 100;
|
||||
$armorInventoryItems[$armorSlot] = Item::safeNbtDeserialize($item, "Human armor slot $armorSlot");
|
||||
}elseif($slot >= 9 && $slot < $this->inventory->getSize() + 9){
|
||||
$inventoryItems[$slot - 9] = Item::nbtDeserialize($item);
|
||||
$inventorySlot = $slot - 9;
|
||||
$inventoryItems[$inventorySlot] = Item::safeNbtDeserialize($item, "Human inventory slot $inventorySlot");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +322,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
}
|
||||
$offHand = $nbt->getCompoundTag(self::TAG_OFF_HAND_ITEM);
|
||||
if($offHand !== null){
|
||||
$this->offHandInventory->setItem(0, Item::nbtDeserialize($offHand));
|
||||
$this->offHandInventory->setItem(0, Item::safeNbtDeserialize($offHand, "Human off-hand item"));
|
||||
}
|
||||
$this->offHandInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(fn() => NetworkBroadcastUtils::broadcastEntityEvent(
|
||||
$this->getViewers(),
|
||||
@@ -331,8 +333,9 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
if($enderChestInventoryTag !== null){
|
||||
$enderChestInventoryItems = [];
|
||||
|
||||
foreach($enderChestInventoryTag as $i => $item){
|
||||
$enderChestInventoryItems[$item->getByte(SavedItemStackData::TAG_SLOT)] = Item::nbtDeserialize($item);
|
||||
foreach($enderChestInventoryTag as $item){
|
||||
$slot = $item->getByte(SavedItemStackData::TAG_SLOT);
|
||||
$enderChestInventoryItems[$slot] = Item::safeNbtDeserialize($item, "Human ender chest slot $slot");
|
||||
}
|
||||
self::populateInventoryFromListTag($this->enderInventory, $enderChestInventoryItems);
|
||||
}
|
||||
|
223
src/entity/effect/EffectCollection.php
Normal file
223
src/entity/effect/EffectCollection.php
Normal file
@@ -0,0 +1,223 @@
|
||||
<?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\entity\effect;
|
||||
|
||||
use pocketmine\color\Color;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use pocketmine\utils\Utils;
|
||||
use function abs;
|
||||
use function count;
|
||||
use function spl_object_id;
|
||||
|
||||
class EffectCollection{
|
||||
|
||||
/** @var EffectInstance[] */
|
||||
protected array $effects = [];
|
||||
|
||||
/**
|
||||
* @var \Closure[]|ObjectSet
|
||||
* @phpstan-var ObjectSet<\Closure(EffectInstance, bool $replacesOldEffect) : void>
|
||||
*/
|
||||
protected ObjectSet $effectAddHooks;
|
||||
|
||||
/**
|
||||
* @var \Closure[]|ObjectSet
|
||||
* @phpstan-var ObjectSet<\Closure(EffectInstance) : void>
|
||||
*/
|
||||
protected ObjectSet $effectRemoveHooks;
|
||||
|
||||
protected Color $bubbleColor;
|
||||
|
||||
protected bool $onlyAmbientEffects = false;
|
||||
|
||||
/**
|
||||
* Validates whether an effect will be used for bubbles color calculation.
|
||||
*
|
||||
* @phpstan-var \Closure(EffectInstance) : bool
|
||||
*/
|
||||
protected \Closure $effectFilterForBubbles;
|
||||
|
||||
public function __construct(){
|
||||
$this->bubbleColor = new Color(0, 0, 0, 0);
|
||||
$this->effectAddHooks = new ObjectSet();
|
||||
$this->effectRemoveHooks = new ObjectSet();
|
||||
|
||||
$this->setEffectFilterForBubbles(static fn(EffectInstance $e) : bool => $e->isVisible() && $e->getType()->hasBubbles());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the effects in the collection, indexed by spl_object_id of the effect type.
|
||||
* @return EffectInstance[]
|
||||
*/
|
||||
public function all() : array{
|
||||
return $this->effects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all effects.
|
||||
*/
|
||||
public function clear() : void{
|
||||
foreach($this->effects as $effect){
|
||||
$this->remove($effect->getType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the effect with the specified ID.
|
||||
*/
|
||||
public function remove(Effect $effectType) : void{
|
||||
$index = spl_object_id($effectType);
|
||||
if(isset($this->effects[$index])){
|
||||
$effect = $this->effects[$index];
|
||||
|
||||
unset($this->effects[$index]);
|
||||
foreach($this->effectRemoveHooks as $hook){
|
||||
$hook($effect);
|
||||
}
|
||||
|
||||
$this->recalculateEffectColor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effect instance active with the specified ID, or null if does not have the
|
||||
* effect.
|
||||
*/
|
||||
public function get(Effect $effect) : ?EffectInstance{
|
||||
return $this->effects[spl_object_id($effect)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified effect is active.
|
||||
*/
|
||||
public function has(Effect $effect) : bool{
|
||||
return isset($this->effects[spl_object_id($effect)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* In the following cases it will return true:
|
||||
* - if the effect type is not already applied
|
||||
* - if an existing effect of the same type can be replaced (due to shorter duration or lower level)
|
||||
*/
|
||||
public function canAdd(EffectInstance $effect) : bool{
|
||||
$index = spl_object_id($effect->getType());
|
||||
if(isset($this->effects[$index])){
|
||||
$oldEffect = $this->effects[$index];
|
||||
if(
|
||||
abs($effect->getAmplifier()) < $oldEffect->getAmplifier()
|
||||
|| (abs($effect->getAmplifier()) === abs($oldEffect->getAmplifier()) && $effect->getDuration() < $oldEffect->getDuration())
|
||||
){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an effect to the collection.
|
||||
* Existing effects of the same type will be replaced if {@see self::canAdd()} returns true.
|
||||
*
|
||||
* @return bool whether the effect has been successfully applied.
|
||||
*/
|
||||
public function add(EffectInstance $effect) : bool{
|
||||
if($this->canAdd($effect)){
|
||||
$index = spl_object_id($effect->getType());
|
||||
$replacesOldEffect = isset($this->effects[$index]);
|
||||
|
||||
$this->effects[$index] = $effect;
|
||||
foreach($this->effectAddHooks as $hook){
|
||||
$hook($effect, $replacesOldEffect);
|
||||
}
|
||||
|
||||
$this->recalculateEffectColor();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter that determines which effects will be displayed in the bubbles.
|
||||
*
|
||||
* @phpstan-param \Closure(EffectInstance) : bool $filter
|
||||
*/
|
||||
public function setEffectFilterForBubbles(\Closure $filter) : void{
|
||||
Utils::validateCallableSignature(fn(EffectInstance $e) : bool => false, $filter);
|
||||
$this->effectFilterForBubbles = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the potion bubbles colour based on the active effects.
|
||||
*/
|
||||
protected function recalculateEffectColor() : void{
|
||||
/** @var Color[] $colors */
|
||||
$colors = [];
|
||||
$ambient = true;
|
||||
foreach($this->effects as $effect){
|
||||
if(($this->effectFilterForBubbles)($effect)){
|
||||
$level = $effect->getEffectLevel();
|
||||
$color = $effect->getColor();
|
||||
for($i = 0; $i < $level; ++$i){
|
||||
$colors[] = $color;
|
||||
}
|
||||
|
||||
if(!$effect->isAmbient()){
|
||||
$ambient = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(count($colors) > 0){
|
||||
$this->bubbleColor = Color::mix(...$colors);
|
||||
$this->onlyAmbientEffects = $ambient;
|
||||
}else{
|
||||
$this->bubbleColor = new Color(0, 0, 0, 0);
|
||||
$this->onlyAmbientEffects = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBubbleColor() : Color{
|
||||
return $this->bubbleColor;
|
||||
}
|
||||
|
||||
public function hasOnlyAmbientEffects() : bool{
|
||||
return $this->onlyAmbientEffects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure[]|ObjectSet
|
||||
* @phpstan-return ObjectSet<\Closure(EffectInstance, bool $replacesOldEffect) : void>
|
||||
*/
|
||||
public function getEffectAddHooks() : ObjectSet{
|
||||
return $this->effectAddHooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure[]|ObjectSet
|
||||
* @phpstan-return ObjectSet<\Closure(EffectInstance) : void>
|
||||
*/
|
||||
public function getEffectRemoveHooks() : ObjectSet{
|
||||
return $this->effectRemoveHooks;
|
||||
}
|
||||
}
|
@@ -23,56 +23,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\entity\effect;
|
||||
|
||||
use pocketmine\color\Color;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\event\entity\EntityEffectAddEvent;
|
||||
use pocketmine\event\entity\EntityEffectRemoveEvent;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use function abs;
|
||||
use function count;
|
||||
use function spl_object_id;
|
||||
|
||||
class EffectManager{
|
||||
/** @var EffectInstance[] */
|
||||
protected array $effects = [];
|
||||
|
||||
protected Color $bubbleColor;
|
||||
protected bool $onlyAmbientEffects = false;
|
||||
|
||||
/**
|
||||
* @var \Closure[]|ObjectSet
|
||||
* @phpstan-var ObjectSet<\Closure(EffectInstance, bool $replacesOldEffect) : void>
|
||||
*/
|
||||
protected ObjectSet $effectAddHooks;
|
||||
/**
|
||||
* @var \Closure[]|ObjectSet
|
||||
* @phpstan-var ObjectSet<\Closure(EffectInstance) : void>
|
||||
*/
|
||||
protected ObjectSet $effectRemoveHooks;
|
||||
class EffectManager extends EffectCollection{
|
||||
|
||||
public function __construct(
|
||||
private Living $entity
|
||||
){
|
||||
$this->bubbleColor = new Color(0, 0, 0, 0);
|
||||
$this->effectAddHooks = new ObjectSet();
|
||||
$this->effectRemoveHooks = new ObjectSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Effects currently active on the mob.
|
||||
* @return EffectInstance[]
|
||||
*/
|
||||
public function all() : array{
|
||||
return $this->effects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all effects from the mob.
|
||||
*/
|
||||
public function clear() : void{
|
||||
foreach($this->effects as $effect){
|
||||
$this->remove($effect->getType());
|
||||
}
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,55 +53,17 @@ class EffectManager{
|
||||
return;
|
||||
}
|
||||
|
||||
unset($this->effects[$index]);
|
||||
$effect->getType()->remove($this->entity, $effect);
|
||||
foreach($this->effectRemoveHooks as $hook){
|
||||
$hook($effect);
|
||||
}
|
||||
|
||||
$this->recalculateEffectColor();
|
||||
parent::remove($effectType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effect instance active on this entity with the specified ID, or null if the mob does not have the
|
||||
* effect.
|
||||
*/
|
||||
public function get(Effect $effect) : ?EffectInstance{
|
||||
return $this->effects[spl_object_id($effect)] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified effect is active on the mob.
|
||||
*/
|
||||
public function has(Effect $effect) : bool{
|
||||
return isset($this->effects[spl_object_id($effect)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an effect to the mob.
|
||||
* If a weaker effect of the same type is already applied, it will be replaced.
|
||||
* If a weaker or equal-strength effect is already applied but has a shorter duration, it will be replaced.
|
||||
*
|
||||
* @return bool whether the effect has been successfully applied.
|
||||
*/
|
||||
public function add(EffectInstance $effect) : bool{
|
||||
$oldEffect = null;
|
||||
$cancelled = false;
|
||||
|
||||
$index = spl_object_id($effect->getType());
|
||||
if(isset($this->effects[$index])){
|
||||
$oldEffect = $this->effects[$index];
|
||||
if(
|
||||
abs($effect->getAmplifier()) < $oldEffect->getAmplifier()
|
||||
|| (abs($effect->getAmplifier()) === abs($oldEffect->getAmplifier()) && $effect->getDuration() < $oldEffect->getDuration())
|
||||
){
|
||||
$cancelled = true;
|
||||
}
|
||||
}
|
||||
$oldEffect = $this->effects[$index] ?? null;
|
||||
|
||||
$ev = new EntityEffectAddEvent($this->entity, $effect, $oldEffect);
|
||||
if($cancelled){
|
||||
if(!$this->canAdd($effect)){
|
||||
$ev->cancel();
|
||||
}
|
||||
|
||||
@@ -153,53 +77,8 @@ class EffectManager{
|
||||
}
|
||||
|
||||
$effect->getType()->add($this->entity, $effect);
|
||||
foreach($this->effectAddHooks as $hook){
|
||||
$hook($effect, $oldEffect !== null);
|
||||
}
|
||||
|
||||
$this->effects[$index] = $effect;
|
||||
|
||||
$this->recalculateEffectColor();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the mob's potion bubbles colour based on the active effects.
|
||||
*/
|
||||
protected function recalculateEffectColor() : void{
|
||||
/** @var Color[] $colors */
|
||||
$colors = [];
|
||||
$ambient = true;
|
||||
foreach($this->effects as $effect){
|
||||
if($effect->isVisible() && $effect->getType()->hasBubbles()){
|
||||
$level = $effect->getEffectLevel();
|
||||
$color = $effect->getColor();
|
||||
for($i = 0; $i < $level; ++$i){
|
||||
$colors[] = $color;
|
||||
}
|
||||
|
||||
if(!$effect->isAmbient()){
|
||||
$ambient = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(count($colors) > 0){
|
||||
$this->bubbleColor = Color::mix(...$colors);
|
||||
$this->onlyAmbientEffects = $ambient;
|
||||
}else{
|
||||
$this->bubbleColor = new Color(0, 0, 0, 0);
|
||||
$this->onlyAmbientEffects = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBubbleColor() : Color{
|
||||
return $this->bubbleColor;
|
||||
}
|
||||
|
||||
public function hasOnlyAmbientEffects() : bool{
|
||||
return $this->onlyAmbientEffects;
|
||||
return parent::add($effect);
|
||||
}
|
||||
|
||||
public function tick(int $tickDiff = 1) : bool{
|
||||
@@ -216,20 +95,4 @@ class EffectManager{
|
||||
|
||||
return count($this->effects) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure[]|ObjectSet
|
||||
* @phpstan-return ObjectSet<\Closure(EffectInstance, bool $replacesOldEffect) : void>
|
||||
*/
|
||||
public function getEffectAddHooks() : ObjectSet{
|
||||
return $this->effectAddHooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure[]|ObjectSet
|
||||
* @phpstan-return ObjectSet<\Closure(EffectInstance) : void>
|
||||
*/
|
||||
public function getEffectRemoveHooks() : ObjectSet{
|
||||
return $this->effectRemoveHooks;
|
||||
}
|
||||
}
|
||||
|
427
src/entity/object/AreaEffectCloud.php
Normal file
427
src/entity/object/AreaEffectCloud.php
Normal file
@@ -0,0 +1,427 @@
|
||||
<?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\entity\object;
|
||||
|
||||
use pocketmine\data\bedrock\EffectIdMap;
|
||||
use pocketmine\data\bedrock\PotionTypeIds;
|
||||
use pocketmine\entity\effect\EffectCollection;
|
||||
use pocketmine\entity\effect\EffectInstance;
|
||||
use pocketmine\entity\effect\InstantEffect;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\EntitySizeInfo;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\entity\Location;
|
||||
use pocketmine\event\entity\AreaEffectCloudApplyEvent;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\ListTag;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\world\particle\PotionSplashParticle;
|
||||
use function count;
|
||||
use function max;
|
||||
use function round;
|
||||
|
||||
class AreaEffectCloud extends Entity{
|
||||
|
||||
public const DEFAULT_DURATION = 600; // in ticks
|
||||
public const DEFAULT_DURATION_CHANGE_ON_USE = 0; // in ticks
|
||||
|
||||
public const UPDATE_DELAY = 10; // in ticks
|
||||
public const REAPPLICATION_DELAY = 40; // in ticks
|
||||
|
||||
public const DEFAULT_RADIUS = 3.0; // in blocks
|
||||
public const DEFAULT_RADIUS_CHANGE_ON_PICKUP = -0.5; // in blocks
|
||||
public const DEFAULT_RADIUS_CHANGE_ON_USE = -0.5; // in blocks
|
||||
public const DEFAULT_RADIUS_CHANGE_PER_TICK = -(self::DEFAULT_RADIUS / self::DEFAULT_DURATION); // in blocks
|
||||
|
||||
protected const TAG_POTION_ID = "PotionId"; //TAG_Short
|
||||
protected const TAG_SPAWN_TICK = "SpawnTick"; //TAG_Long
|
||||
protected const TAG_DURATION = "Duration"; //TAG_Int
|
||||
protected const TAG_PICKUP_COUNT = "PickupCount"; //TAG_Int
|
||||
protected const TAG_DURATION_ON_USE = "DurationOnUse"; //TAG_Int
|
||||
protected const TAG_REAPPLICATION_DELAY = "ReapplicationDelay"; //TAG_Int
|
||||
protected const TAG_INITIAL_RADIUS = "InitialRadius"; //TAG_Float
|
||||
protected const TAG_RADIUS = "Radius"; //TAG_Float
|
||||
protected const TAG_RADIUS_CHANGE_ON_PICKUP = "RadiusChangeOnPickup"; //TAG_Float
|
||||
protected const TAG_RADIUS_ON_USE = "RadiusOnUse"; //TAG_Float
|
||||
protected const TAG_RADIUS_PER_TICK = "RadiusPerTick"; //TAG_Float
|
||||
protected const TAG_EFFECTS = "mobEffects"; //TAG_List
|
||||
|
||||
public static function getNetworkTypeId() : string{ return EntityIds::AREA_EFFECT_CLOUD; }
|
||||
|
||||
protected int $age = 0;
|
||||
|
||||
protected EffectCollection $effectCollection;
|
||||
|
||||
/** @var array<int, int> entity ID => expiration */
|
||||
protected array $victims = [];
|
||||
|
||||
protected int $maxAge = self::DEFAULT_DURATION;
|
||||
protected int $maxAgeChangeOnUse = self::DEFAULT_DURATION_CHANGE_ON_USE;
|
||||
|
||||
protected int $reapplicationDelay = self::REAPPLICATION_DELAY;
|
||||
|
||||
protected int $pickupCount = 0;
|
||||
protected float $radiusChangeOnPickup = self::DEFAULT_RADIUS_CHANGE_ON_PICKUP;
|
||||
|
||||
protected float $initialRadius = self::DEFAULT_RADIUS;
|
||||
protected float $radius = self::DEFAULT_RADIUS;
|
||||
protected float $radiusChangeOnUse = self::DEFAULT_RADIUS_CHANGE_ON_USE;
|
||||
protected float $radiusChangePerTick = self::DEFAULT_RADIUS_CHANGE_PER_TICK;
|
||||
|
||||
public function __construct(
|
||||
Location $location,
|
||||
?CompoundTag $nbt = null
|
||||
){
|
||||
parent::__construct($location, $nbt);
|
||||
}
|
||||
|
||||
protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(0.5, $this->radius * 2); }
|
||||
|
||||
protected function getInitialDragMultiplier() : float{ return 0.0; }
|
||||
|
||||
protected function getInitialGravity() : float{ return 0.0; }
|
||||
|
||||
protected function initEntity(CompoundTag $nbt) : void{
|
||||
parent::initEntity($nbt);
|
||||
|
||||
$this->effectCollection = new EffectCollection();
|
||||
$this->effectCollection->getEffectAddHooks()->add(function() : void{ $this->networkPropertiesDirty = true; });
|
||||
$this->effectCollection->getEffectRemoveHooks()->add(function() : void{ $this->networkPropertiesDirty = true; });
|
||||
$this->effectCollection->setEffectFilterForBubbles(static fn(EffectInstance $e) : bool => $e->isVisible());
|
||||
|
||||
$worldTime = $this->getWorld()->getTime();
|
||||
$this->age = max($worldTime - $nbt->getLong(self::TAG_SPAWN_TICK, $worldTime), 0);
|
||||
$this->maxAge = $nbt->getInt(self::TAG_DURATION, self::DEFAULT_DURATION);
|
||||
$this->maxAgeChangeOnUse = $nbt->getInt(self::TAG_DURATION_ON_USE, self::DEFAULT_DURATION_CHANGE_ON_USE);
|
||||
$this->pickupCount = $nbt->getInt(self::TAG_PICKUP_COUNT, 0);
|
||||
$this->reapplicationDelay = $nbt->getInt(self::TAG_REAPPLICATION_DELAY, self::REAPPLICATION_DELAY);
|
||||
|
||||
$this->initialRadius = $nbt->getFloat(self::TAG_INITIAL_RADIUS, self::DEFAULT_RADIUS);
|
||||
$this->setRadius($nbt->getFloat(self::TAG_RADIUS, $this->initialRadius));
|
||||
$this->radiusChangeOnPickup = $nbt->getFloat(self::TAG_RADIUS_CHANGE_ON_PICKUP, self::DEFAULT_RADIUS_CHANGE_ON_PICKUP);
|
||||
$this->radiusChangeOnUse = $nbt->getFloat(self::TAG_RADIUS_ON_USE, self::DEFAULT_RADIUS_CHANGE_ON_USE);
|
||||
$this->radiusChangePerTick = $nbt->getFloat(self::TAG_RADIUS_PER_TICK, self::DEFAULT_RADIUS_CHANGE_PER_TICK);
|
||||
|
||||
$effectsTag = $nbt->getListTag(self::TAG_EFFECTS, CompoundTag::class);
|
||||
if($effectsTag !== null){
|
||||
foreach($effectsTag as $e){
|
||||
$effect = EffectIdMap::getInstance()->fromId($e->getByte("Id"));
|
||||
if($effect === null){
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->effectCollection->add(new EffectInstance(
|
||||
$effect,
|
||||
$e->getInt("Duration"),
|
||||
Binary::unsignByte($e->getByte("Amplifier")),
|
||||
$e->getByte("ShowParticles", 1) !== 0,
|
||||
$e->getByte("Ambient", 0) !== 0
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
|
||||
$nbt->setLong(self::TAG_SPAWN_TICK, $this->getWorld()->getTime() - $this->age);
|
||||
$nbt->setShort(self::TAG_POTION_ID, PotionTypeIds::WATER); //not used, mobEffects is used exclusively in Bedrock
|
||||
$nbt->setInt(self::TAG_DURATION, $this->maxAge);
|
||||
$nbt->setInt(self::TAG_DURATION_ON_USE, $this->maxAgeChangeOnUse);
|
||||
$nbt->setInt(self::TAG_PICKUP_COUNT, $this->pickupCount);
|
||||
$nbt->setInt(self::TAG_REAPPLICATION_DELAY, $this->reapplicationDelay);
|
||||
$nbt->setFloat(self::TAG_INITIAL_RADIUS, $this->initialRadius);
|
||||
$nbt->setFloat(self::TAG_RADIUS, $this->radius);
|
||||
$nbt->setFloat(self::TAG_RADIUS_CHANGE_ON_PICKUP, $this->radiusChangeOnPickup);
|
||||
$nbt->setFloat(self::TAG_RADIUS_ON_USE, $this->radiusChangeOnUse);
|
||||
$nbt->setFloat(self::TAG_RADIUS_PER_TICK, $this->radiusChangePerTick);
|
||||
|
||||
if(count($this->effectCollection->all()) > 0){
|
||||
$effects = [];
|
||||
foreach($this->effectCollection->all() as $effect){
|
||||
$effects[] = CompoundTag::create()
|
||||
->setByte("Id", EffectIdMap::getInstance()->toId($effect->getType()))
|
||||
->setByte("Amplifier", Binary::signByte($effect->getAmplifier()))
|
||||
->setInt("Duration", $effect->getDuration())
|
||||
->setByte("Ambient", $effect->isAmbient() ? 1 : 0)
|
||||
->setByte("ShowParticles", $effect->isVisible() ? 1 : 0);
|
||||
}
|
||||
$nbt->setTag(self::TAG_EFFECTS, new ListTag($effects));
|
||||
}
|
||||
|
||||
return $nbt;
|
||||
}
|
||||
|
||||
public function isFireProof() : bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canBeCollidedWith() : bool{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current age of the cloud (in ticks).
|
||||
*/
|
||||
public function getAge() : int{
|
||||
return $this->age;
|
||||
}
|
||||
|
||||
public function getEffects() : EffectCollection{
|
||||
return $this->effectCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initial radius (in blocks).
|
||||
*/
|
||||
public function getInitialRadius() : float{
|
||||
return $this->initialRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current radius (in blocks).
|
||||
*/
|
||||
public function getRadius() : float{
|
||||
return $this->radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current radius (in blocks).
|
||||
*/
|
||||
protected function setRadius(float $radius) : void{
|
||||
$this->radius = $radius;
|
||||
$this->setSize($this->getInitialSizeInfo());
|
||||
$this->networkPropertiesDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount that the radius of this cloud will add by when it is
|
||||
* picked up (in blocks). Usually negative resulting in a radius reduction.
|
||||
*
|
||||
* Applied when getting dragon breath bottle.
|
||||
*/
|
||||
public function getRadiusChangeOnPickup() : float{
|
||||
return $this->radiusChangeOnPickup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount that the radius of this cloud will add by when it is
|
||||
* picked up (in blocks). Usually negative resulting in a radius reduction.
|
||||
*
|
||||
* Applied when getting dragon breath bottle.
|
||||
*/
|
||||
public function setRadiusChangeOnPickup(float $radiusChangeOnPickup) : void{
|
||||
$this->radiusChangeOnPickup = $radiusChangeOnPickup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount that the radius of this cloud will add by when it
|
||||
* applies an effect to an entity (in blocks). Usually negative resulting in a radius reduction.
|
||||
*/
|
||||
public function getRadiusChangeOnUse() : float{
|
||||
return $this->radiusChangeOnUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount that the radius of this cloud will add by when it
|
||||
* applies an effect to an entity (in blocks).
|
||||
*/
|
||||
public function setRadiusChangeOnUse(float $radiusChangeOnUse) : void{
|
||||
$this->radiusChangeOnUse = $radiusChangeOnUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount that the radius of this cloud will add by when an update
|
||||
* is performed (in blocks). Usually negative resulting in a radius reduction.
|
||||
*/
|
||||
public function getRadiusChangePerTick() : float{
|
||||
return $this->radiusChangePerTick;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount that the radius of this cloud will add by when an update is performed (in blocks).
|
||||
*/
|
||||
public function setRadiusChangePerTick(float $radiusChangePerTick) : void{
|
||||
$this->radiusChangePerTick = $radiusChangePerTick;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the age at which the cloud will despawn.
|
||||
*/
|
||||
public function getMaxAge() : int{
|
||||
return $this->maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the age at which the cloud will despawn.
|
||||
*/
|
||||
public function setMaxAge(int $maxAge) : void{
|
||||
$this->maxAge = $maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount that the max age of this cloud will change by when it
|
||||
* applies an effect to an entity (in ticks).
|
||||
*/
|
||||
public function getMaxAgeChangeOnUse() : int{
|
||||
return $this->maxAgeChangeOnUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount that the max age of this cloud will change by when it
|
||||
* applies an effect to an entity (in ticks).
|
||||
*/
|
||||
public function setMaxAgeChangeOnUse(int $maxAgeChangeOnUse) : void{
|
||||
$this->maxAgeChangeOnUse = $maxAgeChangeOnUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time that an entity will be immune from subsequent exposure (in ticks).
|
||||
*/
|
||||
public function getReapplicationDelay() : int{
|
||||
return $this->reapplicationDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the time that an entity will be immune from subsequent exposure (in ticks).
|
||||
*/
|
||||
public function setReapplicationDelay(int $delay) : void{
|
||||
$this->reapplicationDelay = $delay;
|
||||
}
|
||||
|
||||
protected function entityBaseTick(int $tickDiff = 1) : bool{
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
$this->age += $tickDiff;
|
||||
$radius = $this->radius + ($this->radiusChangePerTick * $tickDiff);
|
||||
if($radius < 0.5){
|
||||
$this->flagForDespawn();
|
||||
return true;
|
||||
}
|
||||
$this->setRadius($radius);
|
||||
if($this->age >= self::UPDATE_DELAY && ($this->age % self::UPDATE_DELAY) === 0){
|
||||
if($this->age > $this->maxAge){
|
||||
$this->flagForDespawn();
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach($this->victims as $entityId => $expiration){
|
||||
if($this->age >= $expiration){
|
||||
unset($this->victims[$entityId]);
|
||||
}
|
||||
}
|
||||
|
||||
$entities = [];
|
||||
$radiusChange = 0.0;
|
||||
$maxAgeChange = 0;
|
||||
foreach($this->getWorld()->getCollidingEntities($this->getBoundingBox(), $this) as $entity){
|
||||
if(!$entity instanceof Living || isset($this->victims[$entity->getId()])){
|
||||
continue;
|
||||
}
|
||||
$entityPosition = $entity->getPosition();
|
||||
$xDiff = $entityPosition->getX() - $this->location->getX();
|
||||
$zDiff = $entityPosition->getZ() - $this->location->getZ();
|
||||
if(($xDiff ** 2 + $zDiff ** 2) > $this->radius ** 2){
|
||||
continue;
|
||||
}
|
||||
$entities[] = $entity;
|
||||
if($this->radiusChangeOnUse !== 0.0){
|
||||
$radiusChange += $this->radiusChangeOnUse;
|
||||
if($this->radius + $radiusChange <= 0){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if($this->maxAgeChangeOnUse !== 0){
|
||||
$maxAgeChange += $this->maxAgeChangeOnUse;
|
||||
if($this->maxAge + $maxAgeChange <= 0){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(count($entities) === 0){
|
||||
return $hasUpdate;
|
||||
}
|
||||
|
||||
$ev = new AreaEffectCloudApplyEvent($this, $entities);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
return $hasUpdate;
|
||||
}
|
||||
|
||||
foreach($ev->getAffectedEntities() as $entity){
|
||||
foreach($this->effectCollection->all() as $effect){
|
||||
$effect = clone $effect; //avoid accidental modification
|
||||
if($effect->getType() instanceof InstantEffect){
|
||||
$effect->getType()->applyEffect($entity, $effect, 0.5, $this);
|
||||
}else{
|
||||
$entity->getEffects()->add($effect->setDuration((int) round($effect->getDuration() / 4)));
|
||||
}
|
||||
}
|
||||
if($this->reapplicationDelay !== 0){
|
||||
$this->victims[$entity->getId()] = $this->age + $this->reapplicationDelay;
|
||||
}
|
||||
}
|
||||
|
||||
$radius = $this->radius + $radiusChange;
|
||||
$maxAge = $this->maxAge + $maxAgeChange;
|
||||
if($radius <= 0 || $maxAge <= 0){
|
||||
$this->flagForDespawn();
|
||||
return true;
|
||||
}
|
||||
$this->setRadius($radius);
|
||||
$this->setMaxAge($maxAge);
|
||||
$hasUpdate = true;
|
||||
}
|
||||
|
||||
return $hasUpdate;
|
||||
}
|
||||
|
||||
protected function syncNetworkData(EntityMetadataCollection $properties) : void{
|
||||
parent::syncNetworkData($properties);
|
||||
|
||||
//visual properties
|
||||
$properties->setFloat(EntityMetadataProperties::AREA_EFFECT_CLOUD_RADIUS, $this->radius);
|
||||
$properties->setInt(EntityMetadataProperties::POTION_COLOR, Binary::signInt((
|
||||
count($this->effectCollection->all()) === 0 ? PotionSplashParticle::DEFAULT_COLOR() : $this->effectCollection->getBubbleColor()
|
||||
)->toARGB()));
|
||||
|
||||
//these are properties the client expects, and are used for client-sided logic, which we don't want
|
||||
$properties->setByte(EntityMetadataProperties::POTION_AMBIENT, 0);
|
||||
$properties->setInt(EntityMetadataProperties::AREA_EFFECT_CLOUD_DURATION, -1);
|
||||
$properties->setFloat(EntityMetadataProperties::AREA_EFFECT_CLOUD_RADIUS_CHANGE_ON_PICKUP, 0);
|
||||
$properties->setFloat(EntityMetadataProperties::AREA_EFFECT_CLOUD_RADIUS_PER_TICK, 0);
|
||||
$properties->setInt(EntityMetadataProperties::AREA_EFFECT_CLOUD_SPAWN_TIME, 0);
|
||||
$properties->setFloat(EntityMetadataProperties::AREA_EFFECT_CLOUD_PICKUP_COUNT, 0);
|
||||
$properties->setInt(EntityMetadataProperties::AREA_EFFECT_CLOUD_WAITING, 0);
|
||||
}
|
||||
|
||||
protected function destroyCycles() : void{
|
||||
//wipe out callback refs
|
||||
$this->effectCollection = new EffectCollection();
|
||||
parent::destroyCycles();
|
||||
}
|
||||
}
|
@@ -52,6 +52,8 @@ class EndCrystal extends Entity implements Explosive{
|
||||
protected bool $showBase = false;
|
||||
protected ?Vector3 $beamTarget = null;
|
||||
|
||||
private bool $primed = false;
|
||||
|
||||
protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(2.0, 2.0); }
|
||||
|
||||
protected function getInitialDragMultiplier() : float{ return 1.0; }
|
||||
@@ -88,11 +90,9 @@ class EndCrystal extends Entity implements Explosive{
|
||||
parent::attack($source);
|
||||
if(
|
||||
$source->getCause() !== EntityDamageEvent::CAUSE_VOID &&
|
||||
!$this->isFlaggedForDespawn() &&
|
||||
!$source->isCancelled()
|
||||
){
|
||||
$this->flagForDespawn();
|
||||
$this->explode();
|
||||
$this->primed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,13 @@ class EndCrystal extends Entity implements Explosive{
|
||||
return $nbt;
|
||||
}
|
||||
|
||||
protected function onDeathUpdate(int $tickDiff) : bool{
|
||||
if($this->primed){
|
||||
$this->explode();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function explode() : void{
|
||||
$ev = new EntityPreExplodeEvent($this, 6);
|
||||
$ev->call();
|
||||
|
@@ -32,10 +32,10 @@ use pocketmine\entity\effect\InstantEffect;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\entity\Location;
|
||||
use pocketmine\entity\object\AreaEffectCloud;
|
||||
use pocketmine\event\entity\ProjectileHitBlockEvent;
|
||||
use pocketmine\event\entity\ProjectileHitEntityEvent;
|
||||
use pocketmine\event\entity\ProjectileHitEvent;
|
||||
use pocketmine\item\Potion;
|
||||
use pocketmine\item\PotionType;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
|
||||
@@ -96,8 +96,8 @@ class SplashPotion extends Throwable{
|
||||
$this->getWorld()->addParticle($this->location, $particle);
|
||||
$this->broadcastSound(new PotionSplashSound());
|
||||
|
||||
if($hasEffects){
|
||||
if(!$this->willLinger()){
|
||||
if(!$this->willLinger()){
|
||||
if($hasEffects){
|
||||
foreach($this->getWorld()->getCollidingEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){
|
||||
if($entity instanceof Living){
|
||||
$distanceSquared = $entity->getEyePos()->distanceSquared($this->location);
|
||||
@@ -126,10 +126,18 @@ class SplashPotion extends Throwable{
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
//TODO: lingering potions
|
||||
}
|
||||
}elseif($event instanceof ProjectileHitBlockEvent && $this->getPotionType() === PotionType::WATER){
|
||||
}else{
|
||||
$entity = new AreaEffectCloud(Location::fromObject($this->location->floor()->add(0.5, 0.5, 0.5), $this->getWorld()));
|
||||
foreach($this->potionType->getEffects() as $effect){
|
||||
$entity->getEffects()->add($effect);
|
||||
}
|
||||
if(($owner = $this->getOwningEntity()) !== null && !$owner->isClosed()){
|
||||
$entity->setOwningEntity($owner);
|
||||
}
|
||||
$entity->spawnToAll();
|
||||
}
|
||||
if(!$hasEffects && $event instanceof ProjectileHitBlockEvent && $this->getPotionType() === PotionType::WATER){
|
||||
$blockIn = $event->getBlockHit()->getSide($event->getRayTraceResult()->getHitFace());
|
||||
|
||||
if($blockIn->hasTypeTag(BlockTypeTags::FIRE)){
|
||||
|
64
src/event/entity/AreaEffectCloudApplyEvent.php
Normal file
64
src/event/entity/AreaEffectCloudApplyEvent.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?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\entity;
|
||||
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\entity\object\AreaEffectCloud;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
|
||||
/**
|
||||
* Called when an area effect cloud applies effects to entities.
|
||||
*
|
||||
* @phpstan-extends EntityEvent<AreaEffectCloud>
|
||||
*/
|
||||
class AreaEffectCloudApplyEvent extends EntityEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
/**
|
||||
* @param Living[] $affectedEntities
|
||||
*/
|
||||
public function __construct(
|
||||
AreaEffectCloud $entity,
|
||||
protected array $affectedEntities
|
||||
){
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AreaEffectCloud
|
||||
*/
|
||||
public function getEntity(){
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the affected entities.
|
||||
*
|
||||
* @return Living[]
|
||||
*/
|
||||
public function getAffectedEntities() : array{
|
||||
return $this->affectedEntities;
|
||||
}
|
||||
}
|
@@ -775,6 +775,24 @@ class Item implements \JsonSerializable{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as nbtDeserialize(), but purposely suppresses data errors and returns AIR if deserialization fails.
|
||||
* An error will be logged to the global logger if this happens.
|
||||
*
|
||||
* @param string $errorLogContext Used in log messages if deserialization fails to aid debugging (e.g. inventory owner, slot number, etc.)
|
||||
*/
|
||||
public static function safeNbtDeserialize(CompoundTag $tag, string $errorLogContext, ?\Logger $logger = null) : Item{
|
||||
try{
|
||||
return self::nbtDeserialize($tag);
|
||||
}catch(SavedDataLoadingException $e){
|
||||
//TODO: what if the intention was to suppress logging?
|
||||
$logger ??= \GlobalLogger::get();
|
||||
$logger->error("$errorLogContext: Error deserializing item (item will be replaced by AIR): " . $e->getMessage());
|
||||
//no trace here, otherwise things could get very noisy
|
||||
return VanillaItems::AIR();
|
||||
}
|
||||
}
|
||||
|
||||
public function __clone(){
|
||||
$this->nbt = clone $this->nbt;
|
||||
if($this->blockEntityTag !== null){
|
||||
|
@@ -33,6 +33,16 @@ class SplashPotion extends ProjectileItem{
|
||||
|
||||
private PotionType $potionType = PotionType::WATER;
|
||||
|
||||
public function __construct(
|
||||
ItemIdentifier $identifier,
|
||||
string $name = "Splash Potion",
|
||||
array $enchantmentTags = [],
|
||||
private bool $linger = false
|
||||
){
|
||||
//TODO: remove unnecessary default parameters in PM6, they remain because backward compatibility
|
||||
parent::__construct($identifier, $name, $enchantmentTags);
|
||||
}
|
||||
|
||||
protected function describeState(RuntimeDataDescriber $w) : void{
|
||||
$w->enum($this->potionType);
|
||||
}
|
||||
@@ -52,10 +62,19 @@ class SplashPotion extends ProjectileItem{
|
||||
}
|
||||
|
||||
protected function createEntity(Location $location, Player $thrower) : Throwable{
|
||||
return new SplashPotionEntity($location, $thrower, $this->potionType);
|
||||
$projectile = new SplashPotionEntity($location, $thrower, $this->potionType);
|
||||
$projectile->setLinger($this->linger);
|
||||
return $projectile;
|
||||
}
|
||||
|
||||
public function getThrowForce() : float{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this splash potion will create an area-effect cloud on impact.
|
||||
*/
|
||||
public function willLinger() : bool{
|
||||
return $this->linger;
|
||||
}
|
||||
}
|
||||
|
@@ -1226,6 +1226,7 @@ final class StringToItemParser extends StringToTParser{
|
||||
|
||||
$result->register($prefix("potion"), fn() => Items::POTION()->setType($potionType));
|
||||
$result->register($prefix("splash_potion"), fn() => Items::SPLASH_POTION()->setType($potionType));
|
||||
$result->register($prefix("lingering_potion"), fn() => Items::LINGERING_POTION()->setType($potionType));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -222,6 +222,7 @@ use function strtolower;
|
||||
* @method static Armor LEATHER_CAP()
|
||||
* @method static Armor LEATHER_PANTS()
|
||||
* @method static Armor LEATHER_TUNIC()
|
||||
* @method static SplashPotion LINGERING_POTION()
|
||||
* @method static Item MAGMA_CREAM()
|
||||
* @method static Boat MANGROVE_BOAT()
|
||||
* @method static HangingSign MANGROVE_HANGING_SIGN()
|
||||
@@ -543,6 +544,7 @@ final class VanillaItems{
|
||||
self::register("lapis_lazuli", fn(IID $id) => new Item($id, "Lapis Lazuli"));
|
||||
self::register("lava_bucket", fn(IID $id) => new LiquidBucket($id, "Lava Bucket", Blocks::LAVA()));
|
||||
self::register("leather", fn(IID $id) => new Item($id, "Leather"));
|
||||
self::register("lingering_potion", fn(IID $id) => new SplashPotion($id, "Lingering Potion", linger: true));
|
||||
self::register("magma_cream", fn(IID $id) => new Item($id, "Magma Cream"));
|
||||
self::register("mangrove_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN()));
|
||||
self::register("mangrove_hanging_sign", fn(IID $id) => new HangingSign($id, "Mangrove Hanging Sign", Blocks::MANGROVE_CEILING_CENTER_HANGING_SIGN(), Blocks::MANGROVE_CEILING_EDGES_HANGING_SIGN(), Blocks::MANGROVE_WALL_HANGING_SIGN()));
|
||||
|
@@ -2863,6 +2863,48 @@ final class KnownTranslationFactory{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_TICKOVERLOAD, []);
|
||||
}
|
||||
|
||||
public static function pocketmine_server_url_bugReporting(Translatable|string $bugReportingUrl) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_URL_BUGREPORTING, [
|
||||
"bugReportingUrl" => $bugReportingUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_server_url_discord(Translatable|string $discordUrl) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_URL_DISCORD, [
|
||||
"discordUrl" => $discordUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_server_url_docs(Translatable|string $docsUrl) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_URL_DOCS, [
|
||||
"docsUrl" => $docsUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_server_url_donations(Translatable|string $donationsUrl) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_URL_DONATIONS, [
|
||||
"donationsUrl" => $donationsUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_server_url_freePlugins(Translatable|string $pluginsUrl) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_URL_FREEPLUGINS, [
|
||||
"pluginsUrl" => $pluginsUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_server_url_sourceCode(Translatable|string $sourceUrl) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_URL_SOURCECODE, [
|
||||
"sourceUrl" => $sourceUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_server_url_translations(Translatable|string $translationsUrl) : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_URL_TRANSLATIONS, [
|
||||
"translationsUrl" => $translationsUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function pocketmine_plugins() : Translatable{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGINS, []);
|
||||
}
|
||||
|
@@ -616,6 +616,13 @@ final class KnownTranslationKeys{
|
||||
public const POCKETMINE_SERVER_START = "pocketmine.server.start";
|
||||
public const POCKETMINE_SERVER_STARTFINISHED = "pocketmine.server.startFinished";
|
||||
public const POCKETMINE_SERVER_TICKOVERLOAD = "pocketmine.server.tickOverload";
|
||||
public const POCKETMINE_SERVER_URL_BUGREPORTING = "pocketmine.server.url.bugReporting";
|
||||
public const POCKETMINE_SERVER_URL_DISCORD = "pocketmine.server.url.discord";
|
||||
public const POCKETMINE_SERVER_URL_DOCS = "pocketmine.server.url.docs";
|
||||
public const POCKETMINE_SERVER_URL_DONATIONS = "pocketmine.server.url.donations";
|
||||
public const POCKETMINE_SERVER_URL_FREEPLUGINS = "pocketmine.server.url.freePlugins";
|
||||
public const POCKETMINE_SERVER_URL_SOURCECODE = "pocketmine.server.url.sourceCode";
|
||||
public const POCKETMINE_SERVER_URL_TRANSLATIONS = "pocketmine.server.url.translations";
|
||||
public const POCKETMINE_PLUGINS = "pocketmine_plugins";
|
||||
public const POCKETMINE_WILL_START = "pocketmine_will_start";
|
||||
public const PORT_WARNING = "port_warning";
|
||||
|
5
src/network/mcpe/cache/StaticPacketCache.php
vendored
5
src/network/mcpe/cache/StaticPacketCache.php
vendored
@@ -79,10 +79,7 @@ class StaticPacketCache{
|
||||
$biomeDefinition->id,
|
||||
$biomeDefinition->temperature,
|
||||
$biomeDefinition->downfall,
|
||||
$biomeDefinition->redSporeDensity,
|
||||
$biomeDefinition->blueSporeDensity,
|
||||
$biomeDefinition->ashDensity,
|
||||
$biomeDefinition->whiteAshDensity,
|
||||
$biomeDefinition->foliageSnow,
|
||||
$biomeDefinition->depth,
|
||||
$biomeDefinition->scale,
|
||||
new Color(
|
||||
|
@@ -51,6 +51,9 @@ class PrepareEncryptionTask extends AsyncTask{
|
||||
private string $clientPub,
|
||||
\Closure $onCompletion
|
||||
){
|
||||
//make sure the key is valid before we break the stack trace
|
||||
//TODO: maybe in the future we should require OpenSSLAsymmetricKey here instead of string
|
||||
JwtUtils::parseDerPublicKey($this->clientPub);
|
||||
if(self::$SERVER_PRIVATE_KEY === null){
|
||||
$serverPrivateKey = openssl_pkey_new(["ec" => ["curve_name" => "secp384r1"]]);
|
||||
if($serverPrivateKey === false){
|
||||
|
@@ -56,6 +56,7 @@ use function is_object;
|
||||
use function json_decode;
|
||||
use function md5;
|
||||
use function ord;
|
||||
use function var_export;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
|
||||
/**
|
||||
@@ -114,7 +115,7 @@ class LoginPacketHandler extends PacketHandler{
|
||||
throw new PacketHandlingException("Unexpected type for self-signed certificate chain: " . gettype($chainData) . ", expected object");
|
||||
}
|
||||
try{
|
||||
$chain = $this->defaultJsonMapper()->map($chainData, new LegacyAuthChain());
|
||||
$chain = $this->defaultJsonMapper("Self-signed auth chain JSON")->map($chainData, new LegacyAuthChain());
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
throw PacketHandlingException::wrap($e, "Error mapping self-signed certificate chain");
|
||||
}
|
||||
@@ -132,7 +133,7 @@ class LoginPacketHandler extends PacketHandler{
|
||||
}
|
||||
|
||||
try{
|
||||
$claims = $this->defaultJsonMapper()->map($claimsArray["extraData"], new LegacyAuthIdentityData());
|
||||
$claims = $this->defaultJsonMapper("Self-signed auth JWT 'extraData'")->map($claimsArray["extraData"], new LegacyAuthIdentityData());
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
throw PacketHandlingException::wrap($e, "Error mapping self-signed certificate extraData");
|
||||
}
|
||||
@@ -244,7 +245,7 @@ class LoginPacketHandler extends PacketHandler{
|
||||
throw new PacketHandlingException("Unexpected type for auth info data: " . gettype($authInfoJson) . ", expected object");
|
||||
}
|
||||
|
||||
$mapper = $this->defaultJsonMapper();
|
||||
$mapper = $this->defaultJsonMapper("Root authentication info JSON");
|
||||
try{
|
||||
$clientData = $mapper->map($authInfoJson, new AuthenticationInfo());
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
@@ -258,7 +259,7 @@ class LoginPacketHandler extends PacketHandler{
|
||||
* @throws PacketHandlingException
|
||||
*/
|
||||
protected function mapXboxTokenHeader(array $headerArray) : XboxAuthJwtHeader{
|
||||
$mapper = $this->defaultJsonMapper();
|
||||
$mapper = $this->defaultJsonMapper("OpenID JWT header");
|
||||
try{
|
||||
$header = $mapper->map($headerArray, new XboxAuthJwtHeader());
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
@@ -272,7 +273,7 @@ class LoginPacketHandler extends PacketHandler{
|
||||
* @throws PacketHandlingException
|
||||
*/
|
||||
protected function mapXboxTokenBody(array $bodyArray) : XboxAuthJwtBody{
|
||||
$mapper = $this->defaultJsonMapper();
|
||||
$mapper = $this->defaultJsonMapper("OpenID JWT body");
|
||||
try{
|
||||
$header = $mapper->map($bodyArray, new XboxAuthJwtBody());
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
@@ -291,7 +292,7 @@ class LoginPacketHandler extends PacketHandler{
|
||||
throw PacketHandlingException::wrap($e);
|
||||
}
|
||||
|
||||
$mapper = $this->defaultJsonMapper();
|
||||
$mapper = $this->defaultJsonMapper("ClientData JWT body");
|
||||
try{
|
||||
$clientData = $mapper->map($clientDataClaims, new ClientData());
|
||||
}catch(\JsonMapper_Exception $e){
|
||||
@@ -329,12 +330,21 @@ class LoginPacketHandler extends PacketHandler{
|
||||
$this->server->getAsyncPool()->submitTask(new ProcessLegacyLoginTask($legacyCertificate, $clientDataJwt, rootAuthKeyDer: null, authRequired: $authRequired, onCompletion: $this->authCallback));
|
||||
}
|
||||
|
||||
private function defaultJsonMapper() : \JsonMapper{
|
||||
private function defaultJsonMapper(string $logContext) : \JsonMapper{
|
||||
$mapper = new \JsonMapper();
|
||||
$mapper->bExceptionOnMissingData = true;
|
||||
$mapper->bExceptionOnUndefinedProperty = true;
|
||||
$mapper->undefinedPropertyHandler = $this->warnUndefinedJsonPropertyHandler($logContext);
|
||||
$mapper->bStrictObjectTypeChecking = true;
|
||||
$mapper->bEnforceMapType = false;
|
||||
return $mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return \Closure(object, string, mixed) : void
|
||||
*/
|
||||
private function warnUndefinedJsonPropertyHandler(string $context) : \Closure{
|
||||
return fn(object $object, string $name, mixed $value) => $this->session->getLogger()->warning(
|
||||
"$context: Unexpected JSON property for " . (new \ReflectionClass($object))->getShortName() . ": " . $name . " = " . var_export($value, return: true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -62,6 +62,20 @@ class ResourcePacksPacketHandler extends PacketHandler{
|
||||
*/
|
||||
private const MAX_CONCURRENT_CHUNK_REQUESTS = 1;
|
||||
|
||||
/**
|
||||
* All data/resource_packs/chemistry* packs need to be listed here to get chemistry blocks to render
|
||||
* correctly, unfortunately there doesn't seem to be a better way to do this
|
||||
*/
|
||||
private const CHEMISTRY_RESOURCE_PACKS = [
|
||||
["b41c2785-c512-4a49-af56-3a87afd47c57", "1.21.30"],
|
||||
["a4df0cb3-17be-4163-88d7-fcf7002b935d", "1.21.20"],
|
||||
["d19adffe-a2e1-4b02-8436-ca4583368c89", "1.21.10"],
|
||||
["85d5603d-2824-4b21-8044-34f441f4fce1", "1.21.0"],
|
||||
["e977cd13-0a11-4618-96fb-03dfe9c43608", "1.20.60"],
|
||||
["0674721c-a0aa-41a1-9ba8-1ed33ea3e7ed", "1.20.50"],
|
||||
["0fba4063-dba1-4281-9b89-ff9390653530", "1.0.0"],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var ResourcePack[]
|
||||
* @phpstan-var array<string, ResourcePack>
|
||||
@@ -200,8 +214,10 @@ class ResourcePacksPacketHandler extends PacketHandler{
|
||||
return new ResourcePackStackEntry($pack->getPackId(), $pack->getPackVersion(), ""); //TODO: subpacks
|
||||
}, $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", "");
|
||||
//we support chemistry blocks by default, the client should already have these installed
|
||||
foreach(self::CHEMISTRY_RESOURCE_PACKS as [$uuid, $version]){
|
||||
$stack[] = new ResourcePackStackEntry($uuid, $version, "");
|
||||
}
|
||||
|
||||
//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
|
||||
|
@@ -26,9 +26,11 @@ namespace pocketmine\utils;
|
||||
use function abs;
|
||||
use function date_default_timezone_set;
|
||||
use function date_parse;
|
||||
use function escapeshellarg;
|
||||
use function exec;
|
||||
use function file_get_contents;
|
||||
use function implode;
|
||||
use function floor;
|
||||
use function hexdec;
|
||||
use function ini_get;
|
||||
use function ini_set;
|
||||
use function is_array;
|
||||
@@ -37,6 +39,7 @@ use function json_decode;
|
||||
use function parse_ini_file;
|
||||
use function preg_match;
|
||||
use function readlink;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
use function str_starts_with;
|
||||
@@ -105,40 +108,67 @@ abstract class Timezone{
|
||||
public static function detectSystemTimezone() : string|false{
|
||||
switch(Utils::getOS()){
|
||||
case Utils::OS_WINDOWS:
|
||||
$regex = '/(UTC)(\+*\-*\d*\d*\:*\d*\d*)/';
|
||||
$keyPath = 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation';
|
||||
|
||||
/*
|
||||
* wmic timezone get Caption
|
||||
* Get the timezone offset
|
||||
* Get the timezone offset through the registry
|
||||
*
|
||||
* Sample Output var_dump
|
||||
* array(3) {
|
||||
* [0] =>
|
||||
* string(7) "Caption"
|
||||
* [1] =>
|
||||
* string(20) "(UTC+09:30) Adelaide"
|
||||
* [2] =>
|
||||
* string(0) ""
|
||||
* }
|
||||
* array(13) {
|
||||
* [0]=>
|
||||
* string(0) ""
|
||||
* [1]=>
|
||||
* string(71) "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
|
||||
* [2]=>
|
||||
* string(35) " Bias REG_DWORD 0xfffffe20"
|
||||
* [3]=>
|
||||
* string(43) " DaylightBias REG_DWORD 0xffffffc4"
|
||||
* [4]=>
|
||||
* string(45) " DaylightName REG_SZ @tzres.dll,-571"
|
||||
* [5]=>
|
||||
* string(67) " DaylightStart REG_BINARY 00000000000000000000000000000000"
|
||||
* [6]=>
|
||||
* string(36) " StandardBias REG_DWORD 0x0"
|
||||
* [7]=>
|
||||
* string(45) " StandardName REG_SZ @tzres.dll,-572"
|
||||
* [8]=>
|
||||
* string(67) " StandardStart REG_BINARY 00000000000000000000000000000000"
|
||||
* [9]=>
|
||||
* string(52) " TimeZoneKeyName REG_SZ China Standard Time"
|
||||
* [10]=>
|
||||
* string(51) " DynamicDaylightTimeDisabled REG_DWORD 0x0"
|
||||
* [11]=>
|
||||
* string(45) " ActiveTimeBias REG_DWORD 0xfffffe20"
|
||||
* [12]=>
|
||||
* string(0) ""
|
||||
* }
|
||||
*/
|
||||
exec("wmic timezone get Caption", $output);
|
||||
exec("reg query " . escapeshellarg($keyPath), $output);
|
||||
|
||||
$string = trim(implode("\n", $output));
|
||||
foreach($output as $line){
|
||||
if(preg_match('/ActiveTimeBias\s+REG_DWORD\s+0x([0-9a-fA-F]+)/', $line, $matches) > 0){
|
||||
$offsetMinutes = Binary::signInt((int) hexdec(trim($matches[1])));
|
||||
|
||||
//Detect the Time Zone string
|
||||
preg_match($regex, $string, $matches);
|
||||
if($offsetMinutes === 0){
|
||||
return "UTC";
|
||||
}
|
||||
|
||||
if(!isset($matches[2])){
|
||||
return false;
|
||||
$sign = $offsetMinutes <= 0 ? '+' : '-'; //windows timezone + and - are opposite
|
||||
$absMinutes = abs($offsetMinutes);
|
||||
$hours = floor($absMinutes / 60);
|
||||
$minutes = $absMinutes % 60;
|
||||
|
||||
$offset = sprintf(
|
||||
"%s%02d:%02d",
|
||||
$sign,
|
||||
$hours,
|
||||
$minutes
|
||||
);
|
||||
|
||||
return self::parseOffset($offset);
|
||||
}
|
||||
}
|
||||
|
||||
$offset = $matches[2];
|
||||
|
||||
if($offset === ""){
|
||||
return "UTC";
|
||||
}
|
||||
|
||||
return self::parseOffset($offset);
|
||||
return false;
|
||||
case Utils::OS_LINUX:
|
||||
// Ubuntu / Debian.
|
||||
$data = @file_get_contents('/etc/timezone');
|
||||
|
@@ -38,6 +38,7 @@ use pocketmine\item\VanillaItems;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\math\VoxelRayTrace;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\format\SubChunk;
|
||||
@@ -218,8 +219,9 @@ class Explosion{
|
||||
|
||||
if($distance <= 1){
|
||||
$motion = $entityPos->subtractVector($this->source)->normalize();
|
||||
$exposure = $this->getExposure($this->source, $entity);
|
||||
|
||||
$impact = (1 - $distance) * ($exposure = 1);
|
||||
$impact = (1 - $distance) * $exposure;
|
||||
|
||||
$damage = (int) ((($impact * $impact + $impact) / 2) * 8 * $explosionSize + 1);
|
||||
|
||||
@@ -269,6 +271,51 @@ class Explosion{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the explosion exposure of an entity, used to calculate explosion impact.
|
||||
*/
|
||||
private function getExposure(Vector3 $origin, Entity $entity) : float{
|
||||
$bb = $entity->getBoundingBox();
|
||||
|
||||
$diff = (new Vector3($bb->getXLength(), $bb->getYLength(), $bb->getZLength()))->multiply(2)->add(1, 1, 1);
|
||||
$step = new Vector3(1.0 / $diff->x, 1.0 / $diff->y, 1.0 / $diff->z);
|
||||
|
||||
$xOffset = (1.0 - (floor($diff->x) / $diff->x)) / 2.0;
|
||||
$zOffset = (1.0 - (floor($diff->z) / $diff->z)) / 2.0;
|
||||
|
||||
$checks = 0.0;
|
||||
$hits = 0.0;
|
||||
|
||||
for($x = 0.0; $x <= 1.0; $x += $step->x){
|
||||
for($y = 0.0; $y <= 1.0; $y += $step->y){
|
||||
for($z = 0.0; $z <= 1.0; $z += $step->z){
|
||||
$point = new Vector3(
|
||||
self::lerp($x, $bb->minX, $bb->maxX) + $xOffset,
|
||||
self::lerp($y, $bb->minY, $bb->maxY),
|
||||
self::lerp($z, $bb->minZ, $bb->maxZ) + $zOffset
|
||||
);
|
||||
|
||||
$intercepted = false;
|
||||
|
||||
foreach(VoxelRayTrace::betweenPoints($origin, $point) as $pos){
|
||||
$block = $this->world->getBlock($pos);
|
||||
if($block->calculateIntercept($origin, $point) !== null){
|
||||
$intercepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$intercepted){
|
||||
$hits++;
|
||||
}
|
||||
$checks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $checks > 0.0 ? $hits / $checks : 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a chance between 0 and 1 of creating a fire.
|
||||
* For example, if the chance is 1/3, then that amount of affected blocks will be ignited.
|
||||
@@ -282,4 +329,8 @@ class Explosion{
|
||||
}
|
||||
$this->fireChance = $fireChance;
|
||||
}
|
||||
|
||||
private static function lerp(float $scale, float $a, float $b) : float{
|
||||
return $a + $scale * ($b - $a);
|
||||
}
|
||||
}
|
||||
|
@@ -37,16 +37,7 @@ final class BiomeDefinitionEntryData{
|
||||
public float $downfall;
|
||||
|
||||
/** @required */
|
||||
public float $redSporeDensity;
|
||||
|
||||
/** @required */
|
||||
public float $blueSporeDensity;
|
||||
|
||||
/** @required */
|
||||
public float $ashDensity;
|
||||
|
||||
/** @required */
|
||||
public float $whiteAshDensity;
|
||||
public float $foliageSnow;
|
||||
|
||||
/** @required */
|
||||
public float $depth;
|
||||
|
@@ -5,6 +5,9 @@ class JsonMapper_Exception extends \Exception{}
|
||||
|
||||
class JsonMapper{
|
||||
|
||||
/** @var ?\Closure(object, string, mixed) : void */
|
||||
public $undefinedPropertyHandler = null;
|
||||
|
||||
/**
|
||||
* @template TModel of object
|
||||
*
|
||||
|
@@ -589,10 +589,7 @@ class ParserPacketHandler extends PacketHandler{
|
||||
$data->id = $entry->getId();
|
||||
$data->temperature = round($entry->getTemperature(), 3);
|
||||
$data->downfall = round($entry->getDownfall(), 3);
|
||||
$data->redSporeDensity = round($entry->getRedSporeDensity(), 3);
|
||||
$data->blueSporeDensity = round($entry->getBlueSporeDensity(), 3);
|
||||
$data->ashDensity = round($entry->getAshDensity(), 3);
|
||||
$data->whiteAshDensity = round($entry->getWhiteAshDensity(), 3);
|
||||
$data->foliageSnow = round($entry->getFoliageSnow(), 3);
|
||||
$data->depth = round($entry->getDepth(), 3);
|
||||
$data->scale = round($entry->getScale(), 3);
|
||||
$data->mapWaterColour = $mapWaterColor;
|
||||
|
Reference in New Issue
Block a user