Merge branch 'minor-next' into gameplay-permissions

This commit is contained in:
Dylan K. Taylor
2025-10-07 18:41:07 +01:00
51 changed files with 1286 additions and 292 deletions

View File

@@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Setup PHP and tools - name: Setup PHP and tools
uses: shivammathur/setup-php@2.35.4 uses: shivammathur/setup-php@2.35.5
with: with:
php-version: 8.2 php-version: 8.2

View File

@@ -49,7 +49,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@2.35.4 uses: shivammathur/setup-php@2.35.5
with: with:
php-version: 8.2 php-version: 8.2

View File

@@ -87,7 +87,7 @@ jobs:
submodules: true submodules: true
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@2.35.4 uses: shivammathur/setup-php@2.35.5
with: with:
php-version: ${{ env.PHP_VERSION }} php-version: ${{ env.PHP_VERSION }}
@@ -165,7 +165,7 @@ jobs:
${{ github.workspace }}/core-permissions.rst ${{ github.workspace }}/core-permissions.rst
- name: Create draft release - name: Create draft release
uses: ncipollo/release-action@v1.18.0 uses: ncipollo/release-action@v1.20.0
id: create-draft id: create-draft
with: with:
artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json,${{ github.workspace }}/core-permissions.rst

View File

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

View File

@@ -11,7 +11,7 @@ jobs:
steps: steps:
- name: Remove label - name: Remove label
uses: actions/github-script@v7 uses: actions/github-script@v8
with: with:
github-token: ${{ github.token }} github-token: ${{ github.token }}
script: | script: |

View File

@@ -7,7 +7,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v10
with: with:
days-before-issue-stale: -1 days-before-issue-stale: -1
days-before-issue-close: -1 days-before-issue-close: -1

View File

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

111
changelogs/5.34.md Normal file
View 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
View 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.

View File

@@ -34,15 +34,15 @@
"composer-runtime-api": "^2.0", "composer-runtime-api": "^2.0",
"adhocore/json-comment": "~1.2.0", "adhocore/json-comment": "~1.2.0",
"netresearch/jsonmapper": "~v5.0.0", "netresearch/jsonmapper": "~v5.0.0",
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60", "pocketmine/bedrock-block-upgrade-schema": "~5.2.0+bedrock-1.21.110",
"pocketmine/bedrock-data": "~6.0.0+bedrock-1.21.100", "pocketmine/bedrock-data": "~6.1.0+bedrock-1.21.111",
"pocketmine/bedrock-item-upgrade-schema": "~1.15.0+bedrock-1.21.100", "pocketmine/bedrock-item-upgrade-schema": "~1.16.0+bedrock-1.21.110",
"pocketmine/bedrock-protocol": "~50.0.0+bedrock-1.21.100", "pocketmine/bedrock-protocol": "~51.1.0+bedrock-1.21.111",
"pocketmine/binaryutils": "^0.2.1", "pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2", "pocketmine/callback-validator": "^1.0.2",
"pocketmine/color": "^0.3.0", "pocketmine/color": "^0.3.0",
"pocketmine/errorhandler": "^0.7.0", "pocketmine/errorhandler": "^0.7.0",
"pocketmine/locale-data": "~2.25.0", "pocketmine/locale-data": "~2.26.0",
"pocketmine/log": "^0.4.0", "pocketmine/log": "^0.4.0",
"pocketmine/math": "~1.0.0", "pocketmine/math": "~1.0.0",
"pocketmine/nbt": "~1.2.0", "pocketmine/nbt": "~1.2.0",

62
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "0d71d3fba23ba8c4734cac59b9e10129", "content-hash": "ad9a8e8069598ae5ec679f069461623f",
"packages": [ "packages": [
{ {
"name": "adhocore/json-comment", "name": "adhocore/json-comment",
@@ -178,16 +178,16 @@
}, },
{ {
"name": "pocketmine/bedrock-block-upgrade-schema", "name": "pocketmine/bedrock-block-upgrade-schema",
"version": "5.1.0", "version": "5.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git", "url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
"reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a" "reference": "5d7889c9a1cdf9e3cd814d2a104ad69b75116ec7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/2218512e4b91f5bfd09ef55f7a4c4b04e169e41a", "url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/5d7889c9a1cdf9e3cd814d2a104ad69b75116ec7",
"reference": "2218512e4b91f5bfd09ef55f7a4c4b04e169e41a", "reference": "5d7889c9a1cdf9e3cd814d2a104ad69b75116ec7",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@@ -198,22 +198,22 @@
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves", "description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
"support": { "support": {
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues", "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", "name": "pocketmine/bedrock-data",
"version": "6.0.0+bedrock-1.21.100", "version": "6.1.0+bedrock-1.21.111",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/BedrockData.git", "url": "https://github.com/pmmp/BedrockData.git",
"reference": "edc0d829175e5e1e57c87001acfd03526c63fd34" "reference": "7484fe3c3d7949fd48cc520add4f7eeebc4ba4af"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/edc0d829175e5e1e57c87001acfd03526c63fd34", "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/7484fe3c3d7949fd48cc520add4f7eeebc4ba4af",
"reference": "edc0d829175e5e1e57c87001acfd03526c63fd34", "reference": "7484fe3c3d7949fd48cc520add4f7eeebc4ba4af",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@@ -224,22 +224,22 @@
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
"support": { "support": {
"issues": "https://github.com/pmmp/BedrockData/issues", "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", "name": "pocketmine/bedrock-item-upgrade-schema",
"version": "1.15.0", "version": "1.16.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git", "url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
"reference": "09e0dbe9743f21a76b1fe04b2b4136785775f52b" "reference": "8c48ceaa72d390e89c4dbff9542aa4dfa734057d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/09e0dbe9743f21a76b1fe04b2b4136785775f52b", "url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/8c48ceaa72d390e89c4dbff9542aa4dfa734057d",
"reference": "09e0dbe9743f21a76b1fe04b2b4136785775f52b", "reference": "8c48ceaa72d390e89c4dbff9542aa4dfa734057d",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@@ -250,22 +250,22 @@
"description": "JSON schemas for upgrading items found in older Minecraft: Bedrock world saves", "description": "JSON schemas for upgrading items found in older Minecraft: Bedrock world saves",
"support": { "support": {
"issues": "https://github.com/pmmp/BedrockItemUpgradeSchema/issues", "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", "name": "pocketmine/bedrock-protocol",
"version": "50.0.0+bedrock-1.21.100", "version": "51.1.0+bedrock-1.21.111",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git", "url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "2d7aa27a5537ae593fb1c39158648ea462fef72a" "reference": "e380be227766ea58a874eb7d93de76f21c8ec04b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/2d7aa27a5537ae593fb1c39158648ea462fef72a", "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/e380be227766ea58a874eb7d93de76f21c8ec04b",
"reference": "2d7aa27a5537ae593fb1c39158648ea462fef72a", "reference": "e380be227766ea58a874eb7d93de76f21c8ec04b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -297,9 +297,9 @@
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP", "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
"support": { "support": {
"issues": "https://github.com/pmmp/BedrockProtocol/issues", "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", "name": "pocketmine/binaryutils",
@@ -472,16 +472,16 @@
}, },
{ {
"name": "pocketmine/locale-data", "name": "pocketmine/locale-data",
"version": "2.25.1", "version": "2.26.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/Language.git", "url": "https://github.com/pmmp/Language.git",
"reference": "8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd" "reference": "f791369ae082fc5cdf0f2b0bd683e611ff7f90f6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd", "url": "https://api.github.com/repos/pmmp/Language/zipball/f791369ae082fc5cdf0f2b0bd683e611ff7f90f6",
"reference": "8e6514f5a9638e69cdc2219c775fc7d3bb4c9fdd", "reference": "f791369ae082fc5cdf0f2b0bd683e611ff7f90f6",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@@ -489,9 +489,9 @@
"description": "Language resources used by PocketMine-MP", "description": "Language resources used by PocketMine-MP",
"support": { "support": {
"issues": "https://github.com/pmmp/Language/issues", "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", "name": "pocketmine/log",

View File

@@ -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_error2()));
$this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_devBuild_error3())); $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_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(); $this->forceShutdownExit();
return; return;
@@ -1093,7 +1093,23 @@ class Server{
$this->configGroup->save(); $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_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))))); $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); $forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language);

View File

@@ -31,9 +31,10 @@ use function str_repeat;
final class VersionInfo{ final class VersionInfo{
public const NAME = "PocketMine-MP"; 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 IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable"; 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 * PocketMine-MP-specific version ID for world data. Used to determine what fixes need to be applied to old world

View File

@@ -82,7 +82,7 @@ final class Cauldron extends Transparent{
$this->fill(FillableCauldron::MAX_FILL_LEVEL, VanillaBlocks::LAVA_CAULDRON(), $item, VanillaItems::BUCKET(), $returnedItems); $this->fill(FillableCauldron::MAX_FILL_LEVEL, VanillaBlocks::LAVA_CAULDRON(), $item, VanillaItems::BUCKET(), $returnedItems);
}elseif($item->getTypeId() === ItemTypeIds::POWDER_SNOW_BUCKET){ }elseif($item->getTypeId() === ItemTypeIds::POWDER_SNOW_BUCKET){
//TODO: powder snow cauldron //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){ if($item->getType() === PotionType::WATER){
$this->fill(WaterCauldron::WATER_BOTTLE_FILL_AMOUNT, VanillaBlocks::WATER_CAULDRON(), $item, VanillaItems::GLASS_BOTTLE(), $returnedItems); $this->fill(WaterCauldron::WATER_BOTTLE_FILL_AMOUNT, VanillaBlocks::WATER_CAULDRON(), $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
}else{ }else{

View File

@@ -123,7 +123,7 @@ final class WaterCauldron extends FillableCauldron{
$world->addSound($this->position->add(0.5, 0.5, 0.5), new CauldronAddDyeSound()); $world->addSound($this->position->add(0.5, 0.5, 0.5), new CauldronAddDyeSound());
$item->pop(); $item->pop();
}elseif($item instanceof Potion || $item instanceof SplashPotion){ //TODO: lingering potion }elseif($item instanceof Potion || $item instanceof SplashPotion){
if($item->getType() === PotionType::WATER){ if($item->getType() === PotionType::WATER){
$this->setCustomWaterColor(null)->addFillLevels(self::WATER_BOTTLE_FILL_AMOUNT, $item, VanillaItems::GLASS_BOTTLE(), $returnedItems); $this->setCustomWaterColor(null)->addFillLevels(self::WATER_BOTTLE_FILL_AMOUNT, $item, VanillaItems::GLASS_BOTTLE(), $returnedItems);
}else{ }else{

View File

@@ -93,6 +93,7 @@ class Campfire extends Spawnable implements Container{
$listeners = $this->inventory->getListeners()->toArray(); $listeners = $this->inventory->getListeners()->toArray();
$this->inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization $this->inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
$baseErrorContext = "Campfire ($this->position)";
foreach([ foreach([
[0, self::TAG_FIRST_INPUT_ITEM, self::TAG_FIRST_COOKING_TIME], [0, self::TAG_FIRST_INPUT_ITEM, self::TAG_FIRST_COOKING_TIME],
[1, self::TAG_SECOND_INPUT_ITEM, self::TAG_SECOND_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], [3, self::TAG_FOURTH_INPUT_ITEM, self::TAG_FOURTH_COOKING_TIME],
] as [$slot, $itemTag, $cookingTimeTag]){ ] as [$slot, $itemTag, $cookingTimeTag]){
if(($tag = $nbt->getTag($itemTag)) instanceof CompoundTag){ 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){ if(($tag = $nbt->getTag($cookingTimeTag)) instanceof IntTag){
$this->cookingTimes[$slot] = $tag->getValue(); $this->cookingTimes[$slot] = $tag->getValue();

View File

@@ -76,7 +76,6 @@ final class Cauldron extends Spawnable{
default => throw new AssumptionFailedError("Unexpected potion item type") default => throw new AssumptionFailedError("Unexpected potion item type")
}); });
//TODO: lingering potion
$type = $this->potionItem instanceof Potion || $this->potionItem instanceof SplashPotion ? $this->potionItem->getType() : null; $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)); $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){ $this->potionItem = match($containerType){
self::POTION_CONTAINER_TYPE_NORMAL => VanillaItems::POTION()->setType($potionType), self::POTION_CONTAINER_TYPE_NORMAL => VanillaItems::POTION()->setType($potionType),
self::POTION_CONTAINER_TYPE_SPLASH => VanillaItems::SPLASH_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") default => throw new SavedDataLoadingException("Invalid potion container type ID $containerType")
}; };
}else{ }else{
@@ -115,7 +114,6 @@ final class Cauldron extends Spawnable{
default => throw new AssumptionFailedError("Unexpected potion item type") default => throw new AssumptionFailedError("Unexpected potion item type")
}); });
//TODO: lingering potion
$type = $this->potionItem instanceof Potion || $this->potionItem instanceof SplashPotion ? $this->potionItem->getType() : null; $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)); $nbt->setShort(self::TAG_POTION_ID, $type === null ? self::POTION_ID_NONE : PotionTypeIdMap::getInstance()->toId($type));

View File

@@ -26,7 +26,6 @@ namespace pocketmine\block\tile;
use pocketmine\block\utils\ChiseledBookshelfSlot; use pocketmine\block\utils\ChiseledBookshelfSlot;
use pocketmine\data\bedrock\item\SavedItemData; use pocketmine\data\bedrock\item\SavedItemData;
use pocketmine\data\bedrock\item\SavedItemStackData; use pocketmine\data\bedrock\item\SavedItemStackData;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\inventory\SimpleInventory; use pocketmine\inventory\SimpleInventory;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\Vector3; 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 $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
$newContents = []; $newContents = [];
$errorLogContext = "ChiseledBookshelf ($this->position)";
foreach($inventoryTag as $slot => $itemNBT){ foreach($inventoryTag as $slot => $itemNBT){
try{ $count = $itemNBT->getByte(SavedItemStackData::TAG_COUNT);
$count = $itemNBT->getByte(SavedItemStackData::TAG_COUNT); if($count === 0){
if($count === 0){
continue;
}
$newContents[$slot] = Item::nbtDeserialize($itemNBT);
}catch(SavedDataLoadingException $e){
//TODO: not the best solution
\GlobalLogger::get()->logException($e);
continue; continue;
} }
$newContents[$slot] = Item::safeNbtDeserialize($itemNBT, "$errorLogContext slot $slot");
} }
$inventory->setContents($newContents); $inventory->setContents($newContents);

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace pocketmine\block\tile; namespace pocketmine\block\tile;
use pocketmine\data\bedrock\item\SavedItemStackData; use pocketmine\data\bedrock\item\SavedItemStackData;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\inventory\Inventory; use pocketmine\inventory\Inventory;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\nbt\NBT; use pocketmine\nbt\NBT;
@@ -56,14 +55,10 @@ trait ContainerTrait{
$inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization $inventory->getListeners()->remove(...$listeners); //prevent any events being fired by initialization
$newContents = []; $newContents = [];
$errorLogContext = "Container (" . $this->getPosition() . ")";
foreach($inventoryTag as $itemNBT){ foreach($inventoryTag as $itemNBT){
try{ $slotId = $itemNBT->getByte(SavedItemStackData::TAG_SLOT);
$newContents[$itemNBT->getByte(SavedItemStackData::TAG_SLOT)] = Item::nbtDeserialize($itemNBT); $newContents[$slotId] = Item::safeNbtDeserialize($itemNBT, "$errorLogContext slot $slotId");
}catch(SavedDataLoadingException $e){
//TODO: not the best solution
\GlobalLogger::get()->logException($e);
continue;
}
} }
$inventory->setContents($newContents); $inventory->setContents($newContents);

View File

@@ -51,7 +51,7 @@ class ItemFrame extends Spawnable{
public function readSaveData(CompoundTag $nbt) : void{ public function readSaveData(CompoundTag $nbt) : void{
if(($itemTag = $nbt->getCompoundTag(self::TAG_ITEM)) !== null){ 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){ if($nbt->getTag(self::TAG_ITEM_ROTATION) instanceof FloatTag){
$this->itemRotation = (int) ($nbt->getFloat(self::TAG_ITEM_ROTATION, $this->itemRotation * 45) / 45); $this->itemRotation = (int) ($nbt->getFloat(self::TAG_ITEM_ROTATION, $this->itemRotation * 45) / 45);

View File

@@ -44,7 +44,7 @@ class Jukebox extends Spawnable{
public function readSaveData(CompoundTag $nbt) : void{ public function readSaveData(CompoundTag $nbt) : void{
if(($tag = $nbt->getCompoundTag(self::TAG_RECORD)) !== null){ if(($tag = $nbt->getCompoundTag(self::TAG_RECORD)) !== null){
$record = Item::nbtDeserialize($tag); $record = Item::safeNbtDeserialize($tag, "Jukebox ($this->position) record");
if($record instanceof Record){ if($record instanceof Record){
$this->record = $record; $this->record = $record;
} }

View File

@@ -45,7 +45,7 @@ class Lectern extends Spawnable{
public function readSaveData(CompoundTag $nbt) : void{ public function readSaveData(CompoundTag $nbt) : void{
$this->viewedPage = $nbt->getInt(self::TAG_PAGE, 0); $this->viewedPage = $nbt->getInt(self::TAG_PAGE, 0);
if(($itemTag = $nbt->getCompoundTag(self::TAG_BOOK)) !== null){ 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()){ if($book instanceof WritableBookBase && !$book->isNull()){
$this->book = $book; $this->book = $book;
} }

View File

@@ -109,6 +109,7 @@ final class BlockStateNames{
public const PILLAR_AXIS = "pillar_axis"; public const PILLAR_AXIS = "pillar_axis";
public const PORTAL_AXIS = "portal_axis"; public const PORTAL_AXIS = "portal_axis";
public const POWERED_BIT = "powered_bit"; public const POWERED_BIT = "powered_bit";
public const POWERED_SHELF_TYPE = "powered_shelf_type";
public const PROPAGULE_STAGE = "propagule_stage"; public const PROPAGULE_STAGE = "propagule_stage";
public const RAIL_DATA_BIT = "rail_data_bit"; public const RAIL_DATA_BIT = "rail_data_bit";
public const RAIL_DIRECTION = "rail_direction"; public const RAIL_DIRECTION = "rail_direction";

View File

@@ -42,6 +42,7 @@ final class BlockTypeNames{
public const ACACIA_PLANKS = "minecraft:acacia_planks"; public const ACACIA_PLANKS = "minecraft:acacia_planks";
public const ACACIA_PRESSURE_PLATE = "minecraft:acacia_pressure_plate"; public const ACACIA_PRESSURE_PLATE = "minecraft:acacia_pressure_plate";
public const ACACIA_SAPLING = "minecraft:acacia_sapling"; public const ACACIA_SAPLING = "minecraft:acacia_sapling";
public const ACACIA_SHELF = "minecraft:acacia_shelf";
public const ACACIA_SLAB = "minecraft:acacia_slab"; public const ACACIA_SLAB = "minecraft:acacia_slab";
public const ACACIA_STAIRS = "minecraft:acacia_stairs"; public const ACACIA_STAIRS = "minecraft:acacia_stairs";
public const ACACIA_STANDING_SIGN = "minecraft:acacia_standing_sign"; 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_PLANKS = "minecraft:bamboo_planks";
public const BAMBOO_PRESSURE_PLATE = "minecraft:bamboo_pressure_plate"; public const BAMBOO_PRESSURE_PLATE = "minecraft:bamboo_pressure_plate";
public const BAMBOO_SAPLING = "minecraft:bamboo_sapling"; public const BAMBOO_SAPLING = "minecraft:bamboo_sapling";
public const BAMBOO_SHELF = "minecraft:bamboo_shelf";
public const BAMBOO_SLAB = "minecraft:bamboo_slab"; public const BAMBOO_SLAB = "minecraft:bamboo_slab";
public const BAMBOO_STAIRS = "minecraft:bamboo_stairs"; public const BAMBOO_STAIRS = "minecraft:bamboo_stairs";
public const BAMBOO_STANDING_SIGN = "minecraft:bamboo_standing_sign"; 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_PLANKS = "minecraft:birch_planks";
public const BIRCH_PRESSURE_PLATE = "minecraft:birch_pressure_plate"; public const BIRCH_PRESSURE_PLATE = "minecraft:birch_pressure_plate";
public const BIRCH_SAPLING = "minecraft:birch_sapling"; public const BIRCH_SAPLING = "minecraft:birch_sapling";
public const BIRCH_SHELF = "minecraft:birch_shelf";
public const BIRCH_SLAB = "minecraft:birch_slab"; public const BIRCH_SLAB = "minecraft:birch_slab";
public const BIRCH_STAIRS = "minecraft:birch_stairs"; public const BIRCH_STAIRS = "minecraft:birch_stairs";
public const BIRCH_STANDING_SIGN = "minecraft:birch_standing_sign"; 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 = "minecraft:cave_vines";
public const CAVE_VINES_BODY_WITH_BERRIES = "minecraft:cave_vines_body_with_berries"; 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 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 CHAIN_COMMAND_BLOCK = "minecraft:chain_command_block";
public const CHALKBOARD = "minecraft:chalkboard"; public const CHALKBOARD = "minecraft:chalkboard";
public const CHEMICAL_HEAT = "minecraft:chemical_heat"; public const CHEMICAL_HEAT = "minecraft:chemical_heat";
@@ -207,6 +209,7 @@ final class BlockTypeNames{
public const CHERRY_PLANKS = "minecraft:cherry_planks"; public const CHERRY_PLANKS = "minecraft:cherry_planks";
public const CHERRY_PRESSURE_PLATE = "minecraft:cherry_pressure_plate"; public const CHERRY_PRESSURE_PLATE = "minecraft:cherry_pressure_plate";
public const CHERRY_SAPLING = "minecraft:cherry_sapling"; public const CHERRY_SAPLING = "minecraft:cherry_sapling";
public const CHERRY_SHELF = "minecraft:cherry_shelf";
public const CHERRY_SLAB = "minecraft:cherry_slab"; public const CHERRY_SLAB = "minecraft:cherry_slab";
public const CHERRY_STAIRS = "minecraft:cherry_stairs"; public const CHERRY_STAIRS = "minecraft:cherry_stairs";
public const CHERRY_STANDING_SIGN = "minecraft:cherry_standing_sign"; public const CHERRY_STANDING_SIGN = "minecraft:cherry_standing_sign";
@@ -253,12 +256,17 @@ final class BlockTypeNames{
public const COMPOSTER = "minecraft:composter"; public const COMPOSTER = "minecraft:composter";
public const COMPOUND_CREATOR = "minecraft:compound_creator"; public const COMPOUND_CREATOR = "minecraft:compound_creator";
public const CONDUIT = "minecraft:conduit"; public const CONDUIT = "minecraft:conduit";
public const COPPER_BARS = "minecraft:copper_bars";
public const COPPER_BLOCK = "minecraft:copper_block"; public const COPPER_BLOCK = "minecraft:copper_block";
public const COPPER_BULB = "minecraft:copper_bulb"; public const COPPER_BULB = "minecraft:copper_bulb";
public const COPPER_CHAIN = "minecraft:copper_chain";
public const COPPER_CHEST = "minecraft:copper_chest"; public const COPPER_CHEST = "minecraft:copper_chest";
public const COPPER_DOOR = "minecraft:copper_door"; 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_GRATE = "minecraft:copper_grate";
public const COPPER_LANTERN = "minecraft:copper_lantern";
public const COPPER_ORE = "minecraft:copper_ore"; public const COPPER_ORE = "minecraft:copper_ore";
public const COPPER_TORCH = "minecraft:copper_torch";
public const COPPER_TRAPDOOR = "minecraft:copper_trapdoor"; public const COPPER_TRAPDOOR = "minecraft:copper_trapdoor";
public const CORNFLOWER = "minecraft:cornflower"; public const CORNFLOWER = "minecraft:cornflower";
public const CRACKED_DEEPSLATE_BRICKS = "minecraft:cracked_deepslate_bricks"; 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_PLANKS = "minecraft:crimson_planks";
public const CRIMSON_PRESSURE_PLATE = "minecraft:crimson_pressure_plate"; public const CRIMSON_PRESSURE_PLATE = "minecraft:crimson_pressure_plate";
public const CRIMSON_ROOTS = "minecraft:crimson_roots"; public const CRIMSON_ROOTS = "minecraft:crimson_roots";
public const CRIMSON_SHELF = "minecraft:crimson_shelf";
public const CRIMSON_SLAB = "minecraft:crimson_slab"; public const CRIMSON_SLAB = "minecraft:crimson_slab";
public const CRIMSON_STAIRS = "minecraft:crimson_stairs"; public const CRIMSON_STAIRS = "minecraft:crimson_stairs";
public const CRIMSON_STANDING_SIGN = "minecraft:crimson_standing_sign"; 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_PLANKS = "minecraft:dark_oak_planks";
public const DARK_OAK_PRESSURE_PLATE = "minecraft:dark_oak_pressure_plate"; public const DARK_OAK_PRESSURE_PLATE = "minecraft:dark_oak_pressure_plate";
public const DARK_OAK_SAPLING = "minecraft:dark_oak_sapling"; 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_SLAB = "minecraft:dark_oak_slab";
public const DARK_OAK_STAIRS = "minecraft:dark_oak_stairs"; public const DARK_OAK_STAIRS = "minecraft:dark_oak_stairs";
public const DARK_OAK_TRAPDOOR = "minecraft:dark_oak_trapdoor"; 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 ENDER_CHEST = "minecraft:ender_chest";
public const EXPOSED_CHISELED_COPPER = "minecraft:exposed_chiseled_copper"; public const EXPOSED_CHISELED_COPPER = "minecraft:exposed_chiseled_copper";
public const EXPOSED_COPPER = "minecraft:exposed_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_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_CHEST = "minecraft:exposed_copper_chest";
public const EXPOSED_COPPER_DOOR = "minecraft:exposed_copper_door"; 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_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_COPPER_TRAPDOOR = "minecraft:exposed_copper_trapdoor";
public const EXPOSED_CUT_COPPER = "minecraft:exposed_cut_copper"; 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_SLAB = "minecraft:exposed_cut_copper_slab";
public const EXPOSED_CUT_COPPER_STAIRS = "minecraft:exposed_cut_copper_stairs"; 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_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 FARMLAND = "minecraft:farmland";
public const FENCE_GATE = "minecraft:fence_gate"; public const FENCE_GATE = "minecraft:fence_gate";
public const FERN = "minecraft:fern"; public const FERN = "minecraft:fern";
@@ -660,6 +675,7 @@ final class BlockTypeNames{
public const INVISIBLE_BEDROCK = "minecraft:invisible_bedrock"; public const INVISIBLE_BEDROCK = "minecraft:invisible_bedrock";
public const IRON_BARS = "minecraft:iron_bars"; public const IRON_BARS = "minecraft:iron_bars";
public const IRON_BLOCK = "minecraft:iron_block"; public const IRON_BLOCK = "minecraft:iron_block";
public const IRON_CHAIN = "minecraft:iron_chain";
public const IRON_DOOR = "minecraft:iron_door"; public const IRON_DOOR = "minecraft:iron_door";
public const IRON_ORE = "minecraft:iron_ore"; public const IRON_ORE = "minecraft:iron_ore";
public const IRON_TRAPDOOR = "minecraft:iron_trapdoor"; public const IRON_TRAPDOOR = "minecraft:iron_trapdoor";
@@ -676,6 +692,7 @@ final class BlockTypeNames{
public const JUNGLE_PLANKS = "minecraft:jungle_planks"; public const JUNGLE_PLANKS = "minecraft:jungle_planks";
public const JUNGLE_PRESSURE_PLATE = "minecraft:jungle_pressure_plate"; public const JUNGLE_PRESSURE_PLATE = "minecraft:jungle_pressure_plate";
public const JUNGLE_SAPLING = "minecraft:jungle_sapling"; public const JUNGLE_SAPLING = "minecraft:jungle_sapling";
public const JUNGLE_SHELF = "minecraft:jungle_shelf";
public const JUNGLE_SLAB = "minecraft:jungle_slab"; public const JUNGLE_SLAB = "minecraft:jungle_slab";
public const JUNGLE_STAIRS = "minecraft:jungle_stairs"; public const JUNGLE_STAIRS = "minecraft:jungle_stairs";
public const JUNGLE_STANDING_SIGN = "minecraft:jungle_standing_sign"; 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_PRESSURE_PLATE = "minecraft:mangrove_pressure_plate";
public const MANGROVE_PROPAGULE = "minecraft:mangrove_propagule"; public const MANGROVE_PROPAGULE = "minecraft:mangrove_propagule";
public const MANGROVE_ROOTS = "minecraft:mangrove_roots"; public const MANGROVE_ROOTS = "minecraft:mangrove_roots";
public const MANGROVE_SHELF = "minecraft:mangrove_shelf";
public const MANGROVE_SLAB = "minecraft:mangrove_slab"; public const MANGROVE_SLAB = "minecraft:mangrove_slab";
public const MANGROVE_STAIRS = "minecraft:mangrove_stairs"; public const MANGROVE_STAIRS = "minecraft:mangrove_stairs";
public const MANGROVE_STANDING_SIGN = "minecraft:mangrove_standing_sign"; 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_LOG = "minecraft:oak_log";
public const OAK_PLANKS = "minecraft:oak_planks"; public const OAK_PLANKS = "minecraft:oak_planks";
public const OAK_SAPLING = "minecraft:oak_sapling"; public const OAK_SAPLING = "minecraft:oak_sapling";
public const OAK_SHELF = "minecraft:oak_shelf";
public const OAK_SLAB = "minecraft:oak_slab"; public const OAK_SLAB = "minecraft:oak_slab";
public const OAK_STAIRS = "minecraft:oak_stairs"; public const OAK_STAIRS = "minecraft:oak_stairs";
public const OAK_WOOD = "minecraft:oak_wood"; public const OAK_WOOD = "minecraft:oak_wood";
@@ -858,15 +877,20 @@ final class BlockTypeNames{
public const OXEYE_DAISY = "minecraft:oxeye_daisy"; public const OXEYE_DAISY = "minecraft:oxeye_daisy";
public const OXIDIZED_CHISELED_COPPER = "minecraft:oxidized_chiseled_copper"; public const OXIDIZED_CHISELED_COPPER = "minecraft:oxidized_chiseled_copper";
public const OXIDIZED_COPPER = "minecraft:oxidized_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_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_CHEST = "minecraft:oxidized_copper_chest";
public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door"; 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_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_COPPER_TRAPDOOR = "minecraft:oxidized_copper_trapdoor";
public const OXIDIZED_CUT_COPPER = "minecraft:oxidized_cut_copper"; 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_SLAB = "minecraft:oxidized_cut_copper_slab";
public const OXIDIZED_CUT_COPPER_STAIRS = "minecraft:oxidized_cut_copper_stairs"; 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_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_ICE = "minecraft:packed_ice";
public const PACKED_MUD = "minecraft:packed_mud"; public const PACKED_MUD = "minecraft:packed_mud";
public const PALE_HANGING_MOSS = "minecraft:pale_hanging_moss"; 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_PLANKS = "minecraft:pale_oak_planks";
public const PALE_OAK_PRESSURE_PLATE = "minecraft:pale_oak_pressure_plate"; public const PALE_OAK_PRESSURE_PLATE = "minecraft:pale_oak_pressure_plate";
public const PALE_OAK_SAPLING = "minecraft:pale_oak_sapling"; 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_SLAB = "minecraft:pale_oak_slab";
public const PALE_OAK_STAIRS = "minecraft:pale_oak_stairs"; public const PALE_OAK_STAIRS = "minecraft:pale_oak_stairs";
public const PALE_OAK_STANDING_SIGN = "minecraft:pale_oak_standing_sign"; 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_PLANKS = "minecraft:spruce_planks";
public const SPRUCE_PRESSURE_PLATE = "minecraft:spruce_pressure_plate"; public const SPRUCE_PRESSURE_PLATE = "minecraft:spruce_pressure_plate";
public const SPRUCE_SAPLING = "minecraft:spruce_sapling"; public const SPRUCE_SAPLING = "minecraft:spruce_sapling";
public const SPRUCE_SHELF = "minecraft:spruce_shelf";
public const SPRUCE_SLAB = "minecraft:spruce_slab"; public const SPRUCE_SLAB = "minecraft:spruce_slab";
public const SPRUCE_STAIRS = "minecraft:spruce_stairs"; public const SPRUCE_STAIRS = "minecraft:spruce_stairs";
public const SPRUCE_STANDING_SIGN = "minecraft:spruce_standing_sign"; 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_PLANKS = "minecraft:warped_planks";
public const WARPED_PRESSURE_PLATE = "minecraft:warped_pressure_plate"; public const WARPED_PRESSURE_PLATE = "minecraft:warped_pressure_plate";
public const WARPED_ROOTS = "minecraft:warped_roots"; public const WARPED_ROOTS = "minecraft:warped_roots";
public const WARPED_SHELF = "minecraft:warped_shelf";
public const WARPED_SLAB = "minecraft:warped_slab"; public const WARPED_SLAB = "minecraft:warped_slab";
public const WARPED_STAIRS = "minecraft:warped_stairs"; public const WARPED_STAIRS = "minecraft:warped_stairs";
public const WARPED_STANDING_SIGN = "minecraft:warped_standing_sign"; public const WARPED_STANDING_SIGN = "minecraft:warped_standing_sign";
@@ -1213,10 +1240,14 @@ final class BlockTypeNames{
public const WATERLILY = "minecraft:waterlily"; public const WATERLILY = "minecraft:waterlily";
public const WAXED_CHISELED_COPPER = "minecraft:waxed_chiseled_copper"; public const WAXED_CHISELED_COPPER = "minecraft:waxed_chiseled_copper";
public const WAXED_COPPER = "minecraft:waxed_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_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_CHEST = "minecraft:waxed_copper_chest";
public const WAXED_COPPER_DOOR = "minecraft:waxed_copper_door"; 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_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_COPPER_TRAPDOOR = "minecraft:waxed_copper_trapdoor";
public const WAXED_CUT_COPPER = "minecraft:waxed_cut_copper"; public const WAXED_CUT_COPPER = "minecraft:waxed_cut_copper";
public const WAXED_CUT_COPPER_SLAB = "minecraft:waxed_cut_copper_slab"; 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_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_CHISELED_COPPER = "minecraft:waxed_exposed_chiseled_copper";
public const WAXED_EXPOSED_COPPER = "minecraft:waxed_exposed_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_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_CHEST = "minecraft:waxed_exposed_copper_chest";
public const WAXED_EXPOSED_COPPER_DOOR = "minecraft:waxed_exposed_copper_door"; 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_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_COPPER_TRAPDOOR = "minecraft:waxed_exposed_copper_trapdoor";
public const WAXED_EXPOSED_CUT_COPPER = "minecraft:waxed_exposed_cut_copper"; 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_SLAB = "minecraft:waxed_exposed_cut_copper_slab";
public const WAXED_EXPOSED_CUT_COPPER_STAIRS = "minecraft:waxed_exposed_cut_copper_stairs"; 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_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_CHISELED_COPPER = "minecraft:waxed_oxidized_chiseled_copper";
public const WAXED_OXIDIZED_COPPER = "minecraft:waxed_oxidized_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_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_CHEST = "minecraft:waxed_oxidized_copper_chest";
public const WAXED_OXIDIZED_COPPER_DOOR = "minecraft:waxed_oxidized_copper_door"; 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_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_COPPER_TRAPDOOR = "minecraft:waxed_oxidized_copper_trapdoor";
public const WAXED_OXIDIZED_CUT_COPPER = "minecraft:waxed_oxidized_cut_copper"; 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_SLAB = "minecraft:waxed_oxidized_cut_copper_slab";
public const WAXED_OXIDIZED_CUT_COPPER_STAIRS = "minecraft:waxed_oxidized_cut_copper_stairs"; 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_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_CHISELED_COPPER = "minecraft:waxed_weathered_chiseled_copper";
public const WAXED_WEATHERED_COPPER = "minecraft:waxed_weathered_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_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_CHEST = "minecraft:waxed_weathered_copper_chest";
public const WAXED_WEATHERED_COPPER_DOOR = "minecraft:waxed_weathered_copper_door"; 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_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_COPPER_TRAPDOOR = "minecraft:waxed_weathered_copper_trapdoor";
public const WAXED_WEATHERED_CUT_COPPER = "minecraft:waxed_weathered_cut_copper"; 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_SLAB = "minecraft:waxed_weathered_cut_copper_slab";
public const WAXED_WEATHERED_CUT_COPPER_STAIRS = "minecraft:waxed_weathered_cut_copper_stairs"; 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_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_CHISELED_COPPER = "minecraft:weathered_chiseled_copper";
public const WEATHERED_COPPER = "minecraft:weathered_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_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_CHEST = "minecraft:weathered_copper_chest";
public const WEATHERED_COPPER_DOOR = "minecraft:weathered_copper_door"; 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_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_COPPER_TRAPDOOR = "minecraft:weathered_copper_trapdoor";
public const WEATHERED_CUT_COPPER = "minecraft:weathered_cut_copper"; 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_SLAB = "minecraft:weathered_cut_copper_slab";
public const WEATHERED_CUT_COPPER_STAIRS = "minecraft:weathered_cut_copper_stairs"; 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_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 WEB = "minecraft:web";
public const WEEPING_VINES = "minecraft:weeping_vines"; public const WEEPING_VINES = "minecraft:weeping_vines";
public const WET_SPONGE = "minecraft:wet_sponge"; public const WET_SPONGE = "minecraft:wet_sponge";

View File

@@ -1264,7 +1264,7 @@ final class VanillaBlockMappings{
$reg->mapModel(Model::create(Blocks::CARVED_PUMPKIN(), Ids::CARVED_PUMPKIN)->properties([ $reg->mapModel(Model::create(Blocks::CARVED_PUMPKIN(), Ids::CARVED_PUMPKIN)->properties([
$commonProperties->horizontalFacingCardinal $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([ $reg->mapModel(Model::create(Blocks::CHISELED_BOOKSHELF(), Ids::CHISELED_BOOKSHELF)->properties([
$commonProperties->horizontalFacingSWNE, $commonProperties->horizontalFacingSWNE,
new ValueSetFromIntProperty( 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 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)), 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::LIT_PUMPKIN(), Ids::LIT_PUMPKIN)->properties([$commonProperties->horizontalFacingCardinal]));
$reg->mapModel(Model::create(Blocks::LOOM(), Ids::LOOM)->properties([$commonProperties->horizontalFacingSWNE])); $reg->mapModel(Model::create(Blocks::LOOM(), Ids::LOOM)->properties([$commonProperties->horizontalFacingSWNE]));

View File

@@ -141,7 +141,6 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Block(Ids::CAKE, Blocks::CAKE()); $this->map1to1Block(Ids::CAKE, Blocks::CAKE());
$this->map1to1Block(Ids::CAMPFIRE, Blocks::CAMPFIRE()); $this->map1to1Block(Ids::CAMPFIRE, Blocks::CAMPFIRE());
$this->map1to1Block(Ids::CAULDRON, Blocks::CAULDRON()); $this->map1to1Block(Ids::CAULDRON, Blocks::CAULDRON());
$this->map1to1Block(Ids::CHAIN, Blocks::CHAIN());
$this->map1to1Block(Ids::CHERRY_DOOR, Blocks::CHERRY_DOOR()); $this->map1to1Block(Ids::CHERRY_DOOR, Blocks::CHERRY_DOOR());
$this->map1to1Block(Ids::COMPARATOR, Blocks::REDSTONE_COMPARATOR()); $this->map1to1Block(Ids::COMPARATOR, Blocks::REDSTONE_COMPARATOR());
$this->map1to1Block(Ids::CRIMSON_DOOR, Blocks::CRIMSON_DOOR()); $this->map1to1Block(Ids::CRIMSON_DOOR, Blocks::CRIMSON_DOOR());
@@ -519,6 +518,14 @@ final class ItemSerializerDeserializerRegistrar{
}, },
fn(GoatHorn $item) => GoatHornTypeIdMap::getInstance()->toId($item->getHornType()) 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( $this->map1to1ItemWithMeta(
Ids::MEDICINE, Ids::MEDICINE,
Items::MEDICINE(), Items::MEDICINE(),

View File

@@ -112,7 +112,6 @@ final class ItemTypeNames{
public const CAT_SPAWN_EGG = "minecraft:cat_spawn_egg"; public const CAT_SPAWN_EGG = "minecraft:cat_spawn_egg";
public const CAULDRON = "minecraft:cauldron"; public const CAULDRON = "minecraft:cauldron";
public const CAVE_SPIDER_SPAWN_EGG = "minecraft:cave_spider_spawn_egg"; 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_BOOTS = "minecraft:chainmail_boots";
public const CHAINMAIL_CHESTPLATE = "minecraft:chainmail_chestplate"; public const CHAINMAIL_CHESTPLATE = "minecraft:chainmail_chestplate";
public const CHAINMAIL_HELMET = "minecraft:chainmail_helmet"; 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_GOLEM_SPAWN_EGG = "minecraft:copper_golem_spawn_egg";
public const COPPER_HELMET = "minecraft:copper_helmet"; public const COPPER_HELMET = "minecraft:copper_helmet";
public const COPPER_HOE = "minecraft:copper_hoe"; 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_INGOT = "minecraft:copper_ingot";
public const COPPER_LEGGINGS = "minecraft:copper_leggings"; public const COPPER_LEGGINGS = "minecraft:copper_leggings";
public const COPPER_NUGGET = "minecraft:copper_nugget"; public const COPPER_NUGGET = "minecraft:copper_nugget";

View File

@@ -32,6 +32,7 @@ use pocketmine\data\bedrock\PotionTypeIdMap;
use pocketmine\data\bedrock\PotionTypeIds; use pocketmine\data\bedrock\PotionTypeIds;
use pocketmine\data\SavedDataLoadingException; use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\EntityDataHelper as Helper; use pocketmine\entity\EntityDataHelper as Helper;
use pocketmine\entity\object\AreaEffectCloud;
use pocketmine\entity\object\EndCrystal; use pocketmine\entity\object\EndCrystal;
use pocketmine\entity\object\ExperienceOrb; use pocketmine\entity\object\ExperienceOrb;
use pocketmine\entity\object\FallingBlock; 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 //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 //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{ $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); return new Arrow(Helper::parseLocation($nbt, $world), null, $nbt->getByte(Arrow::TAG_CRIT, 0) === 1, $nbt);
}, ['Arrow', 'minecraft:arrow']); }, ['Arrow', 'minecraft:arrow']);

View File

@@ -309,9 +309,11 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
if($slot >= 0 && $slot < 9){ //Hotbar if($slot >= 0 && $slot < 9){ //Hotbar
//Old hotbar saving stuff, ignore it //Old hotbar saving stuff, ignore it
}elseif($slot >= 100 && $slot < 104){ //Armor }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){ }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); $offHand = $nbt->getCompoundTag(self::TAG_OFF_HAND_ITEM);
if($offHand !== null){ 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->offHandInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(fn() => NetworkBroadcastUtils::broadcastEntityEvent(
$this->getViewers(), $this->getViewers(),
@@ -331,8 +333,9 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
if($enderChestInventoryTag !== null){ if($enderChestInventoryTag !== null){
$enderChestInventoryItems = []; $enderChestInventoryItems = [];
foreach($enderChestInventoryTag as $i => $item){ foreach($enderChestInventoryTag as $item){
$enderChestInventoryItems[$item->getByte(SavedItemStackData::TAG_SLOT)] = Item::nbtDeserialize($item); $slot = $item->getByte(SavedItemStackData::TAG_SLOT);
$enderChestInventoryItems[$slot] = Item::safeNbtDeserialize($item, "Human ender chest slot $slot");
} }
self::populateInventoryFromListTag($this->enderInventory, $enderChestInventoryItems); self::populateInventoryFromListTag($this->enderInventory, $enderChestInventoryItems);
} }

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

View File

@@ -23,56 +23,18 @@ declare(strict_types=1);
namespace pocketmine\entity\effect; namespace pocketmine\entity\effect;
use pocketmine\color\Color;
use pocketmine\entity\Living; use pocketmine\entity\Living;
use pocketmine\event\entity\EntityEffectAddEvent; use pocketmine\event\entity\EntityEffectAddEvent;
use pocketmine\event\entity\EntityEffectRemoveEvent; use pocketmine\event\entity\EntityEffectRemoveEvent;
use pocketmine\utils\ObjectSet;
use function abs;
use function count; use function count;
use function spl_object_id; use function spl_object_id;
class EffectManager{ class EffectManager extends EffectCollection{
/** @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;
public function __construct( public function __construct(
private Living $entity private Living $entity
){ ){
$this->bubbleColor = new Color(0, 0, 0, 0); parent::__construct();
$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());
}
} }
/** /**
@@ -91,55 +53,17 @@ class EffectManager{
return; return;
} }
unset($this->effects[$index]);
$effect->getType()->remove($this->entity, $effect); $effect->getType()->remove($this->entity, $effect);
foreach($this->effectRemoveHooks as $hook){ parent::remove($effectType);
$hook($effect);
}
$this->recalculateEffectColor();
} }
} }
/**
* 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{ public function add(EffectInstance $effect) : bool{
$oldEffect = null;
$cancelled = false;
$index = spl_object_id($effect->getType()); $index = spl_object_id($effect->getType());
if(isset($this->effects[$index])){ $oldEffect = $this->effects[$index] ?? null;
$oldEffect = $this->effects[$index];
if(
abs($effect->getAmplifier()) < $oldEffect->getAmplifier()
|| (abs($effect->getAmplifier()) === abs($oldEffect->getAmplifier()) && $effect->getDuration() < $oldEffect->getDuration())
){
$cancelled = true;
}
}
$ev = new EntityEffectAddEvent($this->entity, $effect, $oldEffect); $ev = new EntityEffectAddEvent($this->entity, $effect, $oldEffect);
if($cancelled){ if(!$this->canAdd($effect)){
$ev->cancel(); $ev->cancel();
} }
@@ -153,53 +77,8 @@ class EffectManager{
} }
$effect->getType()->add($this->entity, $effect); $effect->getType()->add($this->entity, $effect);
foreach($this->effectAddHooks as $hook){
$hook($effect, $oldEffect !== null);
}
$this->effects[$index] = $effect; return parent::add($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;
} }
public function tick(int $tickDiff = 1) : bool{ public function tick(int $tickDiff = 1) : bool{
@@ -216,20 +95,4 @@ class EffectManager{
return count($this->effects) > 0; 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;
}
} }

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

View File

@@ -52,6 +52,8 @@ class EndCrystal extends Entity implements Explosive{
protected bool $showBase = false; protected bool $showBase = false;
protected ?Vector3 $beamTarget = null; protected ?Vector3 $beamTarget = null;
private bool $primed = false;
protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(2.0, 2.0); } protected function getInitialSizeInfo() : EntitySizeInfo{ return new EntitySizeInfo(2.0, 2.0); }
protected function getInitialDragMultiplier() : float{ return 1.0; } protected function getInitialDragMultiplier() : float{ return 1.0; }
@@ -88,11 +90,9 @@ class EndCrystal extends Entity implements Explosive{
parent::attack($source); parent::attack($source);
if( if(
$source->getCause() !== EntityDamageEvent::CAUSE_VOID && $source->getCause() !== EntityDamageEvent::CAUSE_VOID &&
!$this->isFlaggedForDespawn() &&
!$source->isCancelled() !$source->isCancelled()
){ ){
$this->flagForDespawn(); $this->primed = true;
$this->explode();
} }
} }
@@ -125,6 +125,13 @@ class EndCrystal extends Entity implements Explosive{
return $nbt; return $nbt;
} }
protected function onDeathUpdate(int $tickDiff) : bool{
if($this->primed){
$this->explode();
}
return true;
}
public function explode() : void{ public function explode() : void{
$ev = new EntityPreExplodeEvent($this, 6); $ev = new EntityPreExplodeEvent($this, 6);
$ev->call(); $ev->call();

View File

@@ -32,10 +32,10 @@ use pocketmine\entity\effect\InstantEffect;
use pocketmine\entity\Entity; use pocketmine\entity\Entity;
use pocketmine\entity\Living; use pocketmine\entity\Living;
use pocketmine\entity\Location; use pocketmine\entity\Location;
use pocketmine\entity\object\AreaEffectCloud;
use pocketmine\event\entity\ProjectileHitBlockEvent; use pocketmine\event\entity\ProjectileHitBlockEvent;
use pocketmine\event\entity\ProjectileHitEntityEvent; use pocketmine\event\entity\ProjectileHitEntityEvent;
use pocketmine\event\entity\ProjectileHitEvent; use pocketmine\event\entity\ProjectileHitEvent;
use pocketmine\item\Potion;
use pocketmine\item\PotionType; use pocketmine\item\PotionType;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
@@ -96,8 +96,8 @@ class SplashPotion extends Throwable{
$this->getWorld()->addParticle($this->location, $particle); $this->getWorld()->addParticle($this->location, $particle);
$this->broadcastSound(new PotionSplashSound()); $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){ foreach($this->getWorld()->getCollidingEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){
if($entity instanceof Living){ if($entity instanceof Living){
$distanceSquared = $entity->getEyePos()->distanceSquared($this->location); $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()); $blockIn = $event->getBlockHit()->getSide($event->getRayTraceResult()->getHitFace());
if($blockIn->hasTypeTag(BlockTypeTags::FIRE)){ if($blockIn->hasTypeTag(BlockTypeTags::FIRE)){

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

View File

@@ -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(){ public function __clone(){
$this->nbt = clone $this->nbt; $this->nbt = clone $this->nbt;
if($this->blockEntityTag !== null){ if($this->blockEntityTag !== null){

View File

@@ -33,6 +33,16 @@ class SplashPotion extends ProjectileItem{
private PotionType $potionType = PotionType::WATER; 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{ protected function describeState(RuntimeDataDescriber $w) : void{
$w->enum($this->potionType); $w->enum($this->potionType);
} }
@@ -52,10 +62,19 @@ class SplashPotion extends ProjectileItem{
} }
protected function createEntity(Location $location, Player $thrower) : Throwable{ 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{ public function getThrowForce() : float{
return 0.5; return 0.5;
} }
/**
* Returns whether this splash potion will create an area-effect cloud on impact.
*/
public function willLinger() : bool{
return $this->linger;
}
} }

View File

@@ -1226,6 +1226,7 @@ final class StringToItemParser extends StringToTParser{
$result->register($prefix("potion"), fn() => Items::POTION()->setType($potionType)); $result->register($prefix("potion"), fn() => Items::POTION()->setType($potionType));
$result->register($prefix("splash_potion"), fn() => Items::SPLASH_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));
} }
} }

View File

@@ -222,6 +222,7 @@ use function strtolower;
* @method static Armor LEATHER_CAP() * @method static Armor LEATHER_CAP()
* @method static Armor LEATHER_PANTS() * @method static Armor LEATHER_PANTS()
* @method static Armor LEATHER_TUNIC() * @method static Armor LEATHER_TUNIC()
* @method static SplashPotion LINGERING_POTION()
* @method static Item MAGMA_CREAM() * @method static Item MAGMA_CREAM()
* @method static Boat MANGROVE_BOAT() * @method static Boat MANGROVE_BOAT()
* @method static HangingSign MANGROVE_HANGING_SIGN() * @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("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("lava_bucket", fn(IID $id) => new LiquidBucket($id, "Lava Bucket", Blocks::LAVA()));
self::register("leather", fn(IID $id) => new Item($id, "Leather")); 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("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_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())); 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()));

View File

@@ -2863,6 +2863,48 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POCKETMINE_SERVER_TICKOVERLOAD, []); 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{ public static function pocketmine_plugins() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGINS, []); return new Translatable(KnownTranslationKeys::POCKETMINE_PLUGINS, []);
} }

View File

@@ -616,6 +616,13 @@ final class KnownTranslationKeys{
public const POCKETMINE_SERVER_START = "pocketmine.server.start"; public const POCKETMINE_SERVER_START = "pocketmine.server.start";
public const POCKETMINE_SERVER_STARTFINISHED = "pocketmine.server.startFinished"; public const POCKETMINE_SERVER_STARTFINISHED = "pocketmine.server.startFinished";
public const POCKETMINE_SERVER_TICKOVERLOAD = "pocketmine.server.tickOverload"; 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_PLUGINS = "pocketmine_plugins";
public const POCKETMINE_WILL_START = "pocketmine_will_start"; public const POCKETMINE_WILL_START = "pocketmine_will_start";
public const PORT_WARNING = "port_warning"; public const PORT_WARNING = "port_warning";

View File

@@ -79,10 +79,7 @@ class StaticPacketCache{
$biomeDefinition->id, $biomeDefinition->id,
$biomeDefinition->temperature, $biomeDefinition->temperature,
$biomeDefinition->downfall, $biomeDefinition->downfall,
$biomeDefinition->redSporeDensity, $biomeDefinition->foliageSnow,
$biomeDefinition->blueSporeDensity,
$biomeDefinition->ashDensity,
$biomeDefinition->whiteAshDensity,
$biomeDefinition->depth, $biomeDefinition->depth,
$biomeDefinition->scale, $biomeDefinition->scale,
new Color( new Color(

View File

@@ -51,6 +51,9 @@ class PrepareEncryptionTask extends AsyncTask{
private string $clientPub, private string $clientPub,
\Closure $onCompletion \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){ if(self::$SERVER_PRIVATE_KEY === null){
$serverPrivateKey = openssl_pkey_new(["ec" => ["curve_name" => "secp384r1"]]); $serverPrivateKey = openssl_pkey_new(["ec" => ["curve_name" => "secp384r1"]]);
if($serverPrivateKey === false){ if($serverPrivateKey === false){

View File

@@ -56,6 +56,7 @@ use function is_object;
use function json_decode; use function json_decode;
use function md5; use function md5;
use function ord; use function ord;
use function var_export;
use const JSON_THROW_ON_ERROR; 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"); throw new PacketHandlingException("Unexpected type for self-signed certificate chain: " . gettype($chainData) . ", expected object");
} }
try{ try{
$chain = $this->defaultJsonMapper()->map($chainData, new LegacyAuthChain()); $chain = $this->defaultJsonMapper("Self-signed auth chain JSON")->map($chainData, new LegacyAuthChain());
}catch(\JsonMapper_Exception $e){ }catch(\JsonMapper_Exception $e){
throw PacketHandlingException::wrap($e, "Error mapping self-signed certificate chain"); throw PacketHandlingException::wrap($e, "Error mapping self-signed certificate chain");
} }
@@ -132,7 +133,7 @@ class LoginPacketHandler extends PacketHandler{
} }
try{ 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){ }catch(\JsonMapper_Exception $e){
throw PacketHandlingException::wrap($e, "Error mapping self-signed certificate extraData"); 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"); throw new PacketHandlingException("Unexpected type for auth info data: " . gettype($authInfoJson) . ", expected object");
} }
$mapper = $this->defaultJsonMapper(); $mapper = $this->defaultJsonMapper("Root authentication info JSON");
try{ try{
$clientData = $mapper->map($authInfoJson, new AuthenticationInfo()); $clientData = $mapper->map($authInfoJson, new AuthenticationInfo());
}catch(\JsonMapper_Exception $e){ }catch(\JsonMapper_Exception $e){
@@ -258,7 +259,7 @@ class LoginPacketHandler extends PacketHandler{
* @throws PacketHandlingException * @throws PacketHandlingException
*/ */
protected function mapXboxTokenHeader(array $headerArray) : XboxAuthJwtHeader{ protected function mapXboxTokenHeader(array $headerArray) : XboxAuthJwtHeader{
$mapper = $this->defaultJsonMapper(); $mapper = $this->defaultJsonMapper("OpenID JWT header");
try{ try{
$header = $mapper->map($headerArray, new XboxAuthJwtHeader()); $header = $mapper->map($headerArray, new XboxAuthJwtHeader());
}catch(\JsonMapper_Exception $e){ }catch(\JsonMapper_Exception $e){
@@ -272,7 +273,7 @@ class LoginPacketHandler extends PacketHandler{
* @throws PacketHandlingException * @throws PacketHandlingException
*/ */
protected function mapXboxTokenBody(array $bodyArray) : XboxAuthJwtBody{ protected function mapXboxTokenBody(array $bodyArray) : XboxAuthJwtBody{
$mapper = $this->defaultJsonMapper(); $mapper = $this->defaultJsonMapper("OpenID JWT body");
try{ try{
$header = $mapper->map($bodyArray, new XboxAuthJwtBody()); $header = $mapper->map($bodyArray, new XboxAuthJwtBody());
}catch(\JsonMapper_Exception $e){ }catch(\JsonMapper_Exception $e){
@@ -291,7 +292,7 @@ class LoginPacketHandler extends PacketHandler{
throw PacketHandlingException::wrap($e); throw PacketHandlingException::wrap($e);
} }
$mapper = $this->defaultJsonMapper(); $mapper = $this->defaultJsonMapper("ClientData JWT body");
try{ try{
$clientData = $mapper->map($clientDataClaims, new ClientData()); $clientData = $mapper->map($clientDataClaims, new ClientData());
}catch(\JsonMapper_Exception $e){ }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)); $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 = new \JsonMapper();
$mapper->bExceptionOnMissingData = true; $mapper->bExceptionOnMissingData = true;
$mapper->bExceptionOnUndefinedProperty = true; $mapper->undefinedPropertyHandler = $this->warnUndefinedJsonPropertyHandler($logContext);
$mapper->bStrictObjectTypeChecking = true; $mapper->bStrictObjectTypeChecking = true;
$mapper->bEnforceMapType = false; $mapper->bEnforceMapType = false;
return $mapper; 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)
);
}
} }

View File

@@ -62,6 +62,20 @@ class ResourcePacksPacketHandler extends PacketHandler{
*/ */
private const MAX_CONCURRENT_CHUNK_REQUESTS = 1; 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[] * @var ResourcePack[]
* @phpstan-var array<string, ResourcePack> * @phpstan-var array<string, ResourcePack>
@@ -200,8 +214,10 @@ class ResourcePacksPacketHandler extends PacketHandler{
return new ResourcePackStackEntry($pack->getPackId(), $pack->getPackVersion(), ""); //TODO: subpacks return new ResourcePackStackEntry($pack->getPackId(), $pack->getPackVersion(), ""); //TODO: subpacks
}, $this->resourcePackStack); }, $this->resourcePackStack);
//we support chemistry blocks by default, the client should already have this installed //we support chemistry blocks by default, the client should already have these installed
$stack[] = new ResourcePackStackEntry("0fba4063-dba1-4281-9b89-ff9390653530", "1.0.0", ""); 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 //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 //but it does have an annoying side-effect when true: it makes

View File

@@ -26,9 +26,11 @@ namespace pocketmine\utils;
use function abs; use function abs;
use function date_default_timezone_set; use function date_default_timezone_set;
use function date_parse; use function date_parse;
use function escapeshellarg;
use function exec; use function exec;
use function file_get_contents; use function file_get_contents;
use function implode; use function floor;
use function hexdec;
use function ini_get; use function ini_get;
use function ini_set; use function ini_set;
use function is_array; use function is_array;
@@ -37,6 +39,7 @@ use function json_decode;
use function parse_ini_file; use function parse_ini_file;
use function preg_match; use function preg_match;
use function readlink; use function readlink;
use function sprintf;
use function str_contains; use function str_contains;
use function str_replace; use function str_replace;
use function str_starts_with; use function str_starts_with;
@@ -105,40 +108,67 @@ abstract class Timezone{
public static function detectSystemTimezone() : string|false{ public static function detectSystemTimezone() : string|false{
switch(Utils::getOS()){ switch(Utils::getOS()){
case Utils::OS_WINDOWS: case Utils::OS_WINDOWS:
$regex = '/(UTC)(\+*\-*\d*\d*\:*\d*\d*)/'; $keyPath = 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation';
/* /*
* wmic timezone get Caption * Get the timezone offset through the registry
* Get the timezone offset
* *
* Sample Output var_dump * Sample Output var_dump
* array(3) { * array(13) {
* [0] => * [0]=>
* string(7) "Caption" * string(0) ""
* [1] => * [1]=>
* string(20) "(UTC+09:30) Adelaide" * string(71) "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
* [2] => * [2]=>
* string(0) "" * 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 if($offsetMinutes === 0){
preg_match($regex, $string, $matches); return "UTC";
}
if(!isset($matches[2])){ $sign = $offsetMinutes <= 0 ? '+' : '-'; //windows timezone + and - are opposite
return false; $absMinutes = abs($offsetMinutes);
$hours = floor($absMinutes / 60);
$minutes = $absMinutes % 60;
$offset = sprintf(
"%s%02d:%02d",
$sign,
$hours,
$minutes
);
return self::parseOffset($offset);
}
} }
return false;
$offset = $matches[2];
if($offset === ""){
return "UTC";
}
return self::parseOffset($offset);
case Utils::OS_LINUX: case Utils::OS_LINUX:
// Ubuntu / Debian. // Ubuntu / Debian.
$data = @file_get_contents('/etc/timezone'); $data = @file_get_contents('/etc/timezone');

View File

@@ -38,6 +38,7 @@ use pocketmine\item\VanillaItems;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\math\VoxelRayTrace;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use pocketmine\world\format\SubChunk; use pocketmine\world\format\SubChunk;
@@ -218,8 +219,9 @@ class Explosion{
if($distance <= 1){ if($distance <= 1){
$motion = $entityPos->subtractVector($this->source)->normalize(); $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); $damage = (int) ((($impact * $impact + $impact) / 2) * 8 * $explosionSize + 1);
@@ -269,6 +271,51 @@ class Explosion{
return true; 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. * 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. * 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; $this->fireChance = $fireChance;
} }
private static function lerp(float $scale, float $a, float $b) : float{
return $a + $scale * ($b - $a);
}
} }

View File

@@ -37,16 +37,7 @@ final class BiomeDefinitionEntryData{
public float $downfall; public float $downfall;
/** @required */ /** @required */
public float $redSporeDensity; public float $foliageSnow;
/** @required */
public float $blueSporeDensity;
/** @required */
public float $ashDensity;
/** @required */
public float $whiteAshDensity;
/** @required */ /** @required */
public float $depth; public float $depth;

View File

@@ -5,6 +5,9 @@ class JsonMapper_Exception extends \Exception{}
class JsonMapper{ class JsonMapper{
/** @var ?\Closure(object, string, mixed) : void */
public $undefinedPropertyHandler = null;
/** /**
* @template TModel of object * @template TModel of object
* *

View File

@@ -589,10 +589,7 @@ class ParserPacketHandler extends PacketHandler{
$data->id = $entry->getId(); $data->id = $entry->getId();
$data->temperature = round($entry->getTemperature(), 3); $data->temperature = round($entry->getTemperature(), 3);
$data->downfall = round($entry->getDownfall(), 3); $data->downfall = round($entry->getDownfall(), 3);
$data->redSporeDensity = round($entry->getRedSporeDensity(), 3); $data->foliageSnow = round($entry->getFoliageSnow(), 3);
$data->blueSporeDensity = round($entry->getBlueSporeDensity(), 3);
$data->ashDensity = round($entry->getAshDensity(), 3);
$data->whiteAshDensity = round($entry->getWhiteAshDensity(), 3);
$data->depth = round($entry->getDepth(), 3); $data->depth = round($entry->getDepth(), 3);
$data->scale = round($entry->getScale(), 3); $data->scale = round($entry->getScale(), 3);
$data->mapWaterColour = $mapWaterColor; $data->mapWaterColour = $mapWaterColor;