Merge branch 'major-next' into inventory-rework

This commit is contained in:
Dylan T. 2024-12-06 12:58:03 +00:00 committed by GitHub
commit 6578d65cd8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 1858 additions and 959 deletions

View File

@ -3,9 +3,6 @@ contact_links:
- name: Help & support on Discord - name: Help & support on Discord
url: https://discord.gg/bmSAZBG url: https://discord.gg/bmSAZBG
about: We don't accept support requests on the issue tracker. Please try asking on Discord instead. about: We don't accept support requests on the issue tracker. Please try asking on Discord instead.
- name: Help & support on forums
url: https://forums.pmmp.io
about: We don't accept support requests on the issue tracker. Please try asking on forums instead.
- name: Documentation - name: Documentation
url: https://pmmp.rtfd.io url: https://pmmp.rtfd.io
about: PocketMine-MP documentation about: PocketMine-MP documentation

View File

@ -37,4 +37,7 @@ updates:
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: "/" directory: "/"
schedule: schedule:
interval: weekly interval: monthly
groups:
github-actions:
patterns: ["*"]

View File

@ -53,7 +53,7 @@ jobs:
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
- name: Build image for tag - name: Build image for tag
uses: docker/build-push-action@v6.9.0 uses: docker/build-push-action@v6.10.0
with: with:
push: true push: true
context: ./pocketmine-mp context: ./pocketmine-mp
@ -66,7 +66,7 @@ jobs:
- name: Build image for major tag - name: Build image for major tag
if: steps.channel.outputs.CHANNEL == 'stable' if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v6.9.0 uses: docker/build-push-action@v6.10.0
with: with:
push: true push: true
context: ./pocketmine-mp context: ./pocketmine-mp
@ -79,7 +79,7 @@ jobs:
- name: Build image for minor tag - name: Build image for minor tag
if: steps.channel.outputs.CHANNEL == 'stable' if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v6.9.0 uses: docker/build-push-action@v6.10.0
with: with:
push: true push: true
context: ./pocketmine-mp context: ./pocketmine-mp
@ -92,7 +92,7 @@ jobs:
- name: Build image for latest tag - name: Build image for latest tag
if: steps.channel.outputs.CHANNEL == 'stable' if: steps.channel.outputs.CHANNEL == 'stable'
uses: docker/build-push-action@v6.9.0 uses: docker/build-push-action@v6.10.0
with: with:
push: true push: true
context: ./pocketmine-mp context: ./pocketmine-mp

View File

@ -64,6 +64,7 @@ jobs:
id: get-pm-version id: get-pm-version
run: | run: |
echo PM_VERSION=$(php build/dump-version-info.php base_version) >> $GITHUB_OUTPUT echo PM_VERSION=$(php build/dump-version-info.php base_version) >> $GITHUB_OUTPUT
echo PM_MAJOR=$(php build/dump-version-info.php major_version) >> $GITHUB_OUTPUT
echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT echo MCPE_VERSION=$(php build/dump-version-info.php mcpe_version) >> $GITHUB_OUTPUT
echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT echo CHANGELOG_FILE_NAME=$(php build/dump-version-info.php changelog_file_name) >> $GITHUB_OUTPUT
echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT echo CHANGELOG_MD_HEADER=$(php build/dump-version-info.php changelog_md_header) >> $GITHUB_OUTPUT
@ -72,7 +73,7 @@ jobs:
- name: Generate PHP binary download URL - name: Generate PHP binary download URL
id: php-binary-url id: php-binary-url
run: | run: |
echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT echo PHP_BINARY_URL="${{ github.server_url }}/${{ github.repository_owner }}/PHP-Binaries/releases/tag/pm${{ steps.get-pm-version.outputs.PM_MAJOR }}-php-${{ matrix.php-version }}-latest" >> $GITHUB_OUTPUT
- name: Generate build info - name: Generate build info
run: | run: |

View File

@ -30,7 +30,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup PHP - name: Setup PHP
uses: pmmp/setup-php-action@3.1.0 uses: pmmp/setup-php-action@3.2.0
with: with:
php-version: ${{ inputs.php }} php-version: ${{ inputs.php }}
install-path: "./bin" install-path: "./bin"
@ -62,7 +62,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup PHP - name: Setup PHP
uses: pmmp/setup-php-action@3.1.0 uses: pmmp/setup-php-action@3.2.0
with: with:
php-version: ${{ inputs.php }} php-version: ${{ inputs.php }}
install-path: "./bin" install-path: "./bin"
@ -96,7 +96,7 @@ jobs:
submodules: true submodules: true
- name: Setup PHP - name: Setup PHP
uses: pmmp/setup-php-action@3.1.0 uses: pmmp/setup-php-action@3.2.0
with: with:
php-version: ${{ inputs.php }} php-version: ${{ inputs.php }}
install-path: "./bin" install-path: "./bin"
@ -128,7 +128,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup PHP - name: Setup PHP
uses: pmmp/setup-php-action@3.1.0 uses: pmmp/setup-php-action@3.2.0
with: with:
php-version: ${{ inputs.php }} php-version: ${{ inputs.php }}
install-path: "./bin" install-path: "./bin"

29
.github/workflows/pr-stale.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: 'Clean up stale PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-issue-stale: -1
days-before-issue-close: -1
stale-pr-message: |
This PR has been marked as "Waiting on Author", but we haven't seen any activity in 7 days.
If there is no further activity, it will be closed in 28 days.
Note for maintainers: Adding an assignee to the PR will prevent it from being marked as stale.
close-pr-message: |
As this PR hasn't been updated for a while, unfortunately we'll have to close it.
days-before-pr-stale: 7
days-before-pr-close: 28
only-labels: "Status: Waiting on Author"
close-pr-label: "Resolution: Abandoned"
exempt-all-assignees: true

View File

@ -123,7 +123,7 @@ The following are required as a minimum for pull requests. PRs that don't meet t
- Remember, PRs with small diffs are much easier to review. Small PRs are generally reviewed and merged much faster than large ones. - Remember, PRs with small diffs are much easier to review. Small PRs are generally reviewed and merged much faster than large ones.
- **Start small.** Try fixing minor bugs or doing something isolated (e.g. adding a new block or item) before attempting larger changes. - **Start small.** Try fixing minor bugs or doing something isolated (e.g. adding a new block or item) before attempting larger changes.
- This helps you get familiar with the codebase, the contribution process, and the expectations of maintainers. - This helps you get familiar with the codebase, the contribution process, and the expectations of maintainers.
- Check out the [issues page]() for something that you could tackle without too much effort. - Check out ["Easy task" issues](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue+is%3Aopen+label%3A%22Easy+task%22) on the issues page for something that you could tackle without too much effort.
- **Do not copy-paste other people's code**. Many PRs involve discussion about the changes, and changes are often requested by reviewers. If you don't understand the code you're copy-pasting, your PR is likely to fail. - **Do not copy-paste other people's code**. Many PRs involve discussion about the changes, and changes are often requested by reviewers. If you don't understand the code you're copy-pasting, your PR is likely to fail.
- **Do not edit code directly on github.com.** We recommend learning how to use [`git`](https://git-scm.com). `git` allows you to "clone" a repository onto your computer, so that you can make changes using an IDE. - **Do not edit code directly on github.com.** We recommend learning how to use [`git`](https://git-scm.com). `git` allows you to "clone" a repository onto your computer, so that you can make changes using an IDE.
- **Use an IDE, not a text editor.** We recommend PhpStorm or VSCode. - **Use an IDE, not a text editor.** We recommend PhpStorm or VSCode.

View File

@ -36,6 +36,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
*/ */
$options = [ $options = [
"base_version" => VersionInfo::BASE_VERSION, "base_version" => VersionInfo::BASE_VERSION,
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK, "mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD, "is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
"changelog_file_name" => function() : string{ "changelog_file_name" => function() : string{

@ -1 +1 @@
Subproject commit a51259d7a6ea649d64f409fc0276baa59cf4f19a Subproject commit 5016e0a3d54c714c12b331ea0474a6f500ffc0a3

View File

@ -110,3 +110,19 @@ Released 12th November 2024.
- Fixed garbage collector cycle count increase on player disconnect. - Fixed garbage collector cycle count increase on player disconnect.
- Fixed weakness effect being applied to all attack types, causing damage splash potions to become weaker. - Fixed weakness effect being applied to all attack types, causing damage splash potions to become weaker.
- Fixed Enchanted Golden Apple regeneration effect amplifier to match vanilla. - Fixed Enchanted Golden Apple regeneration effect amplifier to match vanilla.
# 5.21.2
Released 29th November 2024.
## Fixes
- Fixed blocks destroyable by water being able to be placed inside water.
- Fixed deprecation warnings about nullable typehints on PHP 8.4.
- Fixed `Utils::getNiceClosureName()` not working correctly on PHP 8.4.
- Fixed incorrect break animations when breaking the block behind an instantly-breakable block like a torch.
- Fixed candle extinguish logic.
- Fixed various documentation issues around array key types.
- Introduced a new PHPStan rule along with `Utils::promoteKeys()` to improve PHPStan error reporting around unspecified array key types. Previously, some errors were missed due to PHPStan's BenevolentUnionType.
## DevOps
- `pmmp/server-developers` team is now automatically requested for review on any new PR.
- `Status: Waiting on Author` label is now automatically removed from PRs when they are updated.

16
changelogs/5.22.md Normal file
View File

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

116
changelogs/5.23.md Normal file
View File

@ -0,0 +1,116 @@
# 5.23.0
Released 5th December 2024.
This is a minor feature release, including new gameplay features, internals improvements, API additions and
deprecations, and improvements to timings.
**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
- `/timings` now supports collecting timings from async task workers. These new timings will be shown alongside `Full Server Tick` timings, but will not be counted in total load.
- Added `/xp` command.
- `start.sh` will now emit warnings when the server process exits with an unusual exit code. This helps to detect unexpected segmentation faults and other kinds of native errors.
## Gameplay
- Added the following new items:
- End Crystal
- Goat Horn (all variants)
- Ice Bomb (from Education Edition)
- Recovery Compass
- Added the following enchantments:
- Frost Walker
- Sugarcane now self-destructs when there is no water adjacent to the base block.
- Added basic support for middle-clicking on entities to get their spawn eggs.
- Added sounds when drinking potions.
- Eating food is now allowed in creative mode and in peaceful difficulty.
## API
### `pocketmine\block`
- Extracted `MultiAnyFacingTrait` and `MultiAnySupportTrait` from `GlowLichen` to enable reuse in other blocks.
- The following API methods have been deprecated:
- `Campfire->getInventory()` - this was added by mistake and can't be well-supported given the way that blocks work
### `pocketmine\command`
- The following classes have been added:
- `ClosureCommand` - allows registering a closure to execute a command
### `pocketmine\event`
- Added APIs to `PlayerInteractEvent` to allow toggling item and block interactions.
- This allows various customisations, such as allowing interactions when sneaking, selectively disabling item or block reactions, etc.
- If both item and block interactions are disabled, the event is **not** cancelled (blocks can still be placed).
- The following API methods have been added:
- `public PlayerInteractEvent->setUseBlock(bool $useBlock) : void`
- `public PlayerInteractEvent->setUseItem(bool $useItem) : void`
- `public PlayerInteractEvent->useBlock() : bool` - returns whether the block can respond to the interaction (toggling levers, opening/closing doors, etc).
- `public PlayerInteractEvent->useItem() : bool` - returns whether the item can respond to the interaction (spawn eggs, flint & steel, etc).
- The following new classes have been added:
- `player\PlayerEntityPickEvent` - called when a player middle-clicks on an entity
### `pocketmine\inventory\transaction`
- The following API methods have been deprecated:
- `InventoryAction->onAddToTransaction()`
### `pocketmine\permission`
- The following API methods have been deprecated:
- `PermissionManager->getPermissionSubscriptions()`
- `PermissionManager->subscribeToPermission()`
- `PermissionManager->unsubscribeFromAllPermissions()`
- `PermissionManager->unsubscribeFromPermission()`
### `pocketmine\plugin`
- The following classes have been deprecated:
- `DiskResourceProvider`
- `ResourceProvider`
### `pocketmine\promise`
- `Promise::all()` now accepts zero promises. This will return an already-resolved promise with an empty array.
### `pocketmine\scheduler`
- Added PHPStan generic types to `TaskHandler` and related APIs in `TaskScheduler` and `Task`.
- The following API methods have been deprecated
- `AsyncTask->publishProgress()`
- `AsyncTask->onProgressUpdate()`
### `pocketmine\timings`
- Timings can now notify other code when timings are enabled/disabled, reloaded, or collected.
- The intent of this is to facilitate timings usage on other threads, and have the results collected into a single timings report.
- Timings cannot directly control timings on other threads, so these callbacks allow plugins to use custom mechanisms to toggle, reset and collect timings.
- PocketMine-MP currently uses this to collect timings from async task workers. More internal threads may be supported in the future.
- The following API methods have been added:
- `public static TimingsHandler::getCollectCallbacks() : ObjectSet<\Closure() : list<Promise<list<string>>>` - callbacks for (asynchronously) collecting timings (typically from other threads). The returned promises should be resolved with the result of `TimingsHandler::printCurrentThreadRecords()`.
- `public static TimingsHandler::getReloadCallbacks() : ObjectSet<\Closure() : void>` - callbacks called when timings are reset
- `public static TimingsHandler::getToggleCallbacks() : ObjectSet<\Closure(bool $enable) : void>` - callbacks called when timings are enabled/disabled
- `public static TimingsHandler::requestPrintTimings() : Promise<list<string>>` - asynchronously collects timing results from all threads and assembles them into a single report
- The following API methods have been deprecated:
- `TimingsHandler::printTimings()` - this function cannot support async timings collection. Use `TimingsHandler::requestPrintTimings()` instead.
- `Timings::getAsyncTaskErrorTimings()` - internal method that is no longer needed
- The following constants have been deprecated:
- `Timings::GROUP_BREAKDOWN` - no longer used
### `pocketmine\utils`
- The following API methods have been added:
- `public static Utils::getRandomFloat() : float` - returns a random float between 0 and 1. Drop-in replacement for `lcg_value()` in PHP 8.4.
## Internals
- Blocks are now always synced with the client during a right-click-block interaction. This clears mispredictions on the client in case the new `PlayerInteractEvent` flags were customized by plugins.
- `VanillaBlocks` and `VanillaItems` now use reflection to lookup TypeId constants by registration name, instead of requiring TypeIds to be manually specified.
- While this is obviously a hack, it prevents incorrect constants from being used when adding new blocks, and guarantees that the names of constants in `BlockTypeIds` and `ItemTypeIds` will match their corresponding entries in `VanillaBlocks` and `VanillaItems` respectively.
- It also significantly improves readability of `VanillaBlocks` and `VanillaItems`, as well as eliminating ugly code like `WoodLikeBlockIdHelper`.
- In PM6, the team is exploring options to redesign `VanillaBlocks` and `VanillaItems` to eliminate the need for defining separate TypeIds entirely.
- `ConsoleReader` now uses socket support in `proc_open()` to transmit IPC messages to the server process. Previously, a temporary socket server was used, which was unreliable in some conditions.
- Event handler tests have been converted to PHPUnit tests by mocking `Server` and `Plugin` instances. Previously, these required integration tests for these dependencies.
- Fixed various deprecation warnings in PHP 8.4.
- `netresearch/jsonmapper` is now used at `5.0.0`. The PMMP fork of this library has been removed, as it is no longer needed.
# 5.23.1
Released 5th December 2024.
## Fixes
- Fixed signs not creating a tile when placed.
## Internals
- Improved blockstate consistency check to detect tiles disappearing during refactors.

View File

@ -32,16 +32,16 @@
"ext-zlib": ">=1.2.11", "ext-zlib": ">=1.2.11",
"composer-runtime-api": "^2.0", "composer-runtime-api": "^2.0",
"adhocore/json-comment": "~1.2.0", "adhocore/json-comment": "~1.2.0",
"pocketmine/netresearch-jsonmapper": "~v4.4.999", "netresearch/jsonmapper": "~v5.0.0",
"pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40", "pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
"pocketmine/bedrock-data": "~2.14.0+bedrock-1.21.40", "pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50",
"pocketmine/bedrock-item-upgrade-schema": "~1.13.0+bedrock-1.21.40", "pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.40", "pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.50",
"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.21.0", "pocketmine/locale-data": "~2.22.0",
"pocketmine/log": "^0.4.0", "pocketmine/log": "^0.4.0",
"pocketmine/math": "~1.0.0", "pocketmine/math": "~1.0.0",
"pocketmine/nbt": "~1.0.0", "pocketmine/nbt": "~1.0.0",

155
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": "476374fb3d22e26a97c1dea8c6736faf", "content-hash": "732102eca72dc1d29e7b67dfbce07653",
"packages": [ "packages": [
{ {
"name": "adhocore/json-comment", "name": "adhocore/json-comment",
@ -125,6 +125,57 @@
], ],
"time": "2023-11-29T23:19:16+00:00" "time": "2023-11-29T23:19:16+00:00"
}, },
{
"name": "netresearch/jsonmapper",
"version": "v5.0.0",
"source": {
"type": "git",
"url": "https://github.com/cweiske/jsonmapper.git",
"reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c",
"reference": "8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
"squizlabs/php_codesniffer": "~3.5"
},
"type": "library",
"autoload": {
"psr-0": {
"JsonMapper": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"OSL-3.0"
],
"authors": [
{
"name": "Christian Weiske",
"email": "cweiske@cweiske.de",
"homepage": "http://github.com/cweiske/jsonmapper/",
"role": "Developer"
}
],
"description": "Map nested JSON structures onto PHP classes",
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
"source": "https://github.com/cweiske/jsonmapper/tree/v5.0.0"
},
"time": "2024-09-08T10:20:00+00:00"
},
{ {
"name": "pocketmine/bedrock-block-upgrade-schema", "name": "pocketmine/bedrock-block-upgrade-schema",
"version": "5.0.0", "version": "5.0.0",
@ -153,16 +204,16 @@
}, },
{ {
"name": "pocketmine/bedrock-data", "name": "pocketmine/bedrock-data",
"version": "2.14.1+bedrock-1.21.40", "version": "2.15.0+bedrock-1.21.50",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/BedrockData.git", "url": "https://github.com/pmmp/BedrockData.git",
"reference": "4a41864ed09613ecec6791e2ae076a8ec7089cc4" "reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/4a41864ed09613ecec6791e2ae076a8ec7089cc4", "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
"reference": "4a41864ed09613ecec6791e2ae076a8ec7089cc4", "reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@ -173,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/2.14.1+bedrock-1.21.40" "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.21.50"
}, },
"time": "2024-11-12T21:36:20+00:00" "time": "2024-12-04T12:59:12+00:00"
}, },
{ {
"name": "pocketmine/bedrock-item-upgrade-schema", "name": "pocketmine/bedrock-item-upgrade-schema",
"version": "1.13.1", "version": "1.14.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git", "url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
"reference": "1cf81305f2ffcf7dde9577c4f16a55c765192b03" "reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/1cf81305f2ffcf7dde9577c4f16a55c765192b03", "url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/9fc7c9bbb558a017395c1cb7dd819c033ee971bb",
"reference": "1cf81305f2ffcf7dde9577c4f16a55c765192b03", "reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@ -199,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.13.1" "source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.14.0"
}, },
"time": "2024-11-12T21:33:17+00:00" "time": "2024-12-04T12:22:49+00:00"
}, },
{ {
"name": "pocketmine/bedrock-protocol", "name": "pocketmine/bedrock-protocol",
"version": "35.0.0+bedrock-1.21.40", "version": "35.0.0+bedrock-1.21.50",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/BedrockProtocol.git", "url": "https://github.com/pmmp/BedrockProtocol.git",
"reference": "6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459" "reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459", "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
"reference": "6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459", "reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -245,9 +296,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/35.0.0+bedrock-1.21.40" "source": "https://github.com/pmmp/BedrockProtocol/tree/35.0.0+bedrock-1.21.50"
}, },
"time": "2024-10-24T15:45:43+00:00" "time": "2024-12-04T13:02:00+00:00"
}, },
{ {
"name": "pocketmine/binaryutils", "name": "pocketmine/binaryutils",
@ -420,16 +471,16 @@
}, },
{ {
"name": "pocketmine/locale-data", "name": "pocketmine/locale-data",
"version": "2.21.1", "version": "2.22.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/pmmp/Language.git", "url": "https://github.com/pmmp/Language.git",
"reference": "fdba0f764d6281f64e5968dca94fdab96bf4e167" "reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/fdba0f764d6281f64e5968dca94fdab96bf4e167", "url": "https://api.github.com/repos/pmmp/Language/zipball/aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
"reference": "fdba0f764d6281f64e5968dca94fdab96bf4e167", "reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
"shasum": "" "shasum": ""
}, },
"type": "library", "type": "library",
@ -437,9 +488,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.21.1" "source": "https://github.com/pmmp/Language/tree/2.22.0"
}, },
"time": "2024-11-14T23:11:22+00:00" "time": "2024-11-16T13:28:01+00:00"
}, },
{ {
"name": "pocketmine/log", "name": "pocketmine/log",
@ -565,60 +616,6 @@
}, },
"time": "2023-07-14T13:01:49+00:00" "time": "2023-07-14T13:01:49+00:00"
}, },
{
"name": "pocketmine/netresearch-jsonmapper",
"version": "v4.4.999",
"source": {
"type": "git",
"url": "https://github.com/pmmp/netresearch-jsonmapper.git",
"reference": "9a6610033d56e358e86a3e4fd5f87063c7318833"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/netresearch-jsonmapper/zipball/9a6610033d56e358e86a3e4fd5f87063c7318833",
"reference": "9a6610033d56e358e86a3e4fd5f87063c7318833",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=7.1"
},
"replace": {
"netresearch/jsonmapper": "~4.2.0"
},
"require-dev": {
"phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0",
"squizlabs/php_codesniffer": "~3.5"
},
"type": "library",
"autoload": {
"psr-0": {
"JsonMapper": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"OSL-3.0"
],
"authors": [
{
"name": "Christian Weiske",
"email": "cweiske@cweiske.de",
"homepage": "http://github.com/cweiske/jsonmapper/",
"role": "Developer"
}
],
"description": "Fork of netresearch/jsonmapper with security fixes needed by pocketmine/pocketmine-mp",
"support": {
"email": "cweiske@cweiske.de",
"issues": "https://github.com/cweiske/jsonmapper/issues",
"source": "https://github.com/pmmp/netresearch-jsonmapper/tree/v4.4.999"
},
"time": "2024-02-23T13:17:01+00:00"
},
{ {
"name": "pocketmine/raklib", "name": "pocketmine/raklib",
"version": "1.1.1", "version": "1.1.1",

View File

@ -90,6 +90,8 @@ use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver; use pocketmine\promise\PromiseResolver;
use pocketmine\resourcepacks\ResourcePackManager; use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\AsyncPool; use pocketmine\scheduler\AsyncPool;
use pocketmine\scheduler\TimingsCollectionTask;
use pocketmine\scheduler\TimingsControlTask;
use pocketmine\snooze\SleeperHandler; use pocketmine\snooze\SleeperHandler;
use pocketmine\stats\SendUsageTask; use pocketmine\stats\SendUsageTask;
use pocketmine\thread\log\AttachableThreadSafeLogger; use pocketmine\thread\log\AttachableThreadSafeLogger;
@ -343,6 +345,10 @@ class Server{
return $this->maxPlayers; return $this->maxPlayers;
} }
public function setMaxPlayers(int $maxPlayers) : void{
$this->maxPlayers = $maxPlayers;
}
/** /**
* Returns whether the server requires that players be authenticated to Xbox Live. If true, connecting players who * Returns whether the server requires that players be authenticated to Xbox Live. If true, connecting players who
* are not logged into Xbox Live will be disconnected. * are not logged into Xbox Live will be disconnected.
@ -895,7 +901,36 @@ class Server{
$poolSize = max(1, (int) $poolSize); $poolSize = max(1, (int) $poolSize);
} }
TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);
$this->asyncPool = new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt(Yml::MEMORY_ASYNC_WORKER_HARD_LIMIT, 256)), $this->autoloader, $this->logger, $this->tickSleeper); $this->asyncPool = new AsyncPool($poolSize, max(-1, $this->configGroup->getPropertyInt(Yml::MEMORY_ASYNC_WORKER_HARD_LIMIT, 256)), $this->autoloader, $this->logger, $this->tickSleeper);
$this->asyncPool->addWorkerStartHook(function(int $i) : void{
if(TimingsHandler::isEnabled()){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled(true), $i);
}
});
TimingsHandler::getToggleCallbacks()->add(function(bool $enable) : void{
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::setEnabled($enable), $workerId);
}
});
TimingsHandler::getReloadCallbacks()->add(function() : void{
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$this->asyncPool->submitTaskToWorker(TimingsControlTask::reload(), $workerId);
}
});
TimingsHandler::getCollectCallbacks()->add(function() : array{
$promises = [];
foreach($this->asyncPool->getRunningWorkers() as $workerId){
$resolver = new PromiseResolver();
$this->asyncPool->submitTaskToWorker(new TimingsCollectionTask($resolver), $workerId);
$promises[] = $resolver->getPromise();
}
return $promises;
});
$netCompressionThreshold = -1; $netCompressionThreshold = -1;
if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){ if($this->configGroup->getPropertyInt(Yml::NETWORK_BATCH_THRESHOLD, 256) >= 0){
@ -969,9 +1004,6 @@ class Server{
))); )));
$this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName()))); $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_server_license($this->getName())));
TimingsHandler::setEnabled($this->configGroup->getPropertyBool(Yml::SETTINGS_ENABLE_PROFILING, false));
$this->profilingTickRate = $this->configGroup->getPropertyInt(Yml::SETTINGS_PROFILE_REPORT_TRIGGER, self::TARGET_TICKS_PER_SECOND);
DefaultPermissions::registerCorePermissions(); DefaultPermissions::registerCorePermissions();
$this->commandMap = new SimpleCommandMap($this); $this->commandMap = new SimpleCommandMap($this);

View File

@ -31,7 +31,7 @@ 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.21.2"; public const BASE_VERSION = "5.23.2";
public const IS_DEVELOPMENT_BUILD = true; public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable"; public const BUILD_CHANNEL = "stable";

View File

@ -35,10 +35,10 @@ use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction; use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\AnvilFallSound; use pocketmine\world\sound\AnvilFallSound;
use pocketmine\world\sound\Sound; use pocketmine\world\sound\Sound;
use function lcg_value;
use function round; use function round;
class Anvil extends Transparent implements Fallable{ class Anvil extends Transparent implements Fallable{
@ -97,7 +97,7 @@ class Anvil extends Transparent implements Fallable{
} }
public function onHitGround(FallingBlock $blockEntity) : bool{ public function onHitGround(FallingBlock $blockEntity) : bool{
if(lcg_value() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){ if(Utils::getRandomFloat() < 0.05 + (round($blockEntity->getFallDistance()) - 1) * 0.05){
if($this->damage !== self::VERY_DAMAGED){ if($this->damage !== self::VERY_DAMAGED){
$this->damage = $this->damage + 1; $this->damage = $this->damage + 1;
}else{ }else{

View File

@ -69,6 +69,10 @@ class Campfire extends Transparent{
private const UPDATE_INTERVAL_TICKS = 10; private const UPDATE_INTERVAL_TICKS = 10;
/**
* @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block
* has never been set in the world.
*/
protected CampfireInventory $inventory; protected CampfireInventory $inventory;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
@ -136,6 +140,10 @@ class Campfire extends Transparent{
return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)]; return [AxisAlignedBB::one()->trim(Facing::UP, 9 / 16)];
} }
/**
* @deprecated This was added by mistake. It can't be relied on as the inventory won't be initialized if this block
* has never been set in the world.
*/
public function getInventory() : CampfireInventory{ public function getInventory() : CampfireInventory{
return $this->inventory; return $this->inventory;
} }

View File

@ -31,8 +31,8 @@ use pocketmine\event\entity\EntityTrampleFarmlandEvent;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\utils\Utils;
use function intdiv; use function intdiv;
use function lcg_value;
class Farmland extends Transparent{ class Farmland extends Transparent{
public const MAX_WETNESS = 7; public const MAX_WETNESS = 7;
@ -148,7 +148,7 @@ class Farmland extends Transparent{
} }
public function onEntityLand(Entity $entity) : ?float{ public function onEntityLand(Entity $entity) : ?float{
if($entity instanceof Living && lcg_value() < $entity->getFallDistance() - 0.5){ if($entity instanceof Living && Utils::getRandomFloat() < $entity->getFallDistance() - 0.5){
$ev = new EntityTrampleFarmlandEvent($entity, $this); $ev = new EntityTrampleFarmlandEvent($entity, $this);
$ev->call(); $ev->call();
if(!$ev->isCancelled()){ if(!$ev->isCancelled()){

View File

@ -31,13 +31,13 @@ use pocketmine\item\Item;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\BlockTransaction; use pocketmine\world\BlockTransaction;
use pocketmine\world\sound\ItemFrameAddItemSound; use pocketmine\world\sound\ItemFrameAddItemSound;
use pocketmine\world\sound\ItemFrameRemoveItemSound; use pocketmine\world\sound\ItemFrameRemoveItemSound;
use pocketmine\world\sound\ItemFrameRotateItemSound; use pocketmine\world\sound\ItemFrameRotateItemSound;
use function is_infinite; use function is_infinite;
use function is_nan; use function is_nan;
use function lcg_value;
class ItemFrame extends Flowable{ class ItemFrame extends Flowable{
use AnyFacingTrait; use AnyFacingTrait;
@ -154,7 +154,7 @@ class ItemFrame extends Flowable{
return false; return false;
} }
$world = $this->position->getWorld(); $world = $this->position->getWorld();
if(lcg_value() <= $this->itemDropChance){ if(Utils::getRandomFloat() <= $this->itemDropChance){
$world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem); $world->dropItem($this->position->add(0.5, 0.5, 0.5), clone $this->framedItem);
$world->addSound($this->position, new ItemFrameRemoveItemSound()); $world->addSound($this->position, new ItemFrameRemoveItemSound());
} }
@ -185,7 +185,7 @@ class ItemFrame extends Flowable{
public function getDropsForCompatibleTool(Item $item) : array{ public function getDropsForCompatibleTool(Item $item) : array{
$drops = parent::getDropsForCompatibleTool($item); $drops = parent::getDropsForCompatibleTool($item);
if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){ if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){
$drops[] = clone $this->framedItem; $drops[] = clone $this->framedItem;
} }

View File

@ -33,9 +33,9 @@ use pocketmine\item\Item;
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\utils\Utils;
use pocketmine\world\sound\FizzSound; use pocketmine\world\sound\FizzSound;
use pocketmine\world\sound\Sound; use pocketmine\world\sound\Sound;
use function lcg_value;
abstract class Liquid extends Transparent{ abstract class Liquid extends Transparent{
public const MAX_DECAY = 7; public const MAX_DECAY = 7;
@ -368,7 +368,7 @@ abstract class Liquid extends Transparent{
protected function liquidCollide(Block $cause, Block $result) : bool{ protected function liquidCollide(Block $cause, Block $result) : bool{
if(BlockEventHelper::form($this, $result, $cause)){ if(BlockEventHelper::form($this, $result, $cause)){
$this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (lcg_value() - lcg_value()) * 0.8)); $this->position->getWorld()->addSound($this->position->add(0.5, 0.5, 0.5), new FizzSound(2.6 + (Utils::getRandomFloat() - Utils::getRandomFloat()) * 0.8));
} }
return true; return true;
} }

View File

@ -39,7 +39,7 @@ class Magma extends Opaque{
} }
public function onEntityInside(Entity $entity) : bool{ public function onEntityInside(Entity $entity) : bool{
if($entity instanceof Living && !$entity->isSneaking()){ if($entity instanceof Living && !$entity->isSneaking() && $entity->getFrostWalkerLevel() === 0){
$ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1); $ev = new EntityDamageByBlockEvent($this, $entity, EntityDamageEvent::CAUSE_FIRE, 1);
$entity->attack($ev); $entity->attack($ev);
} }

View File

@ -54,6 +54,7 @@ use pocketmine\block\tile\MonsterSpawner as TileMonsterSpawner;
use pocketmine\block\tile\NormalFurnace as TileNormalFurnace; use pocketmine\block\tile\NormalFurnace as TileNormalFurnace;
use pocketmine\block\tile\Note as TileNote; use pocketmine\block\tile\Note as TileNote;
use pocketmine\block\tile\ShulkerBox as TileShulkerBox; use pocketmine\block\tile\ShulkerBox as TileShulkerBox;
use pocketmine\block\tile\Sign as TileSign;
use pocketmine\block\tile\Smoker as TileSmoker; use pocketmine\block\tile\Smoker as TileSmoker;
use pocketmine\block\tile\Tile; use pocketmine\block\tile\Tile;
use pocketmine\block\utils\AmethystTrait; use pocketmine\block\utils\AmethystTrait;
@ -1359,8 +1360,8 @@ final class VanillaBlocks{
WoodType::WARPED => VanillaItems::WARPED_SIGN(...), WoodType::WARPED => VanillaItems::WARPED_SIGN(...),
WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...), WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...),
}; };
self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem)); self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class);
self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem)); self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class);
} }
} }

View File

@ -0,0 +1,60 @@
<?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\command;
use pocketmine\lang\Translatable;
use pocketmine\utils\Utils;
/**
* @phpstan-type Execute \Closure(CommandSender $sender, Command $command, string $commandLabel, list<string> $args) : mixed
*/
final class ClosureCommand extends Command{
/** @phpstan-var Execute */
private \Closure $execute;
/**
* @param string[] $permissions
* @phpstan-param Execute $execute
*/
public function __construct(
string $name,
\Closure $execute,
array $permissions,
Translatable|string $description = "",
Translatable|string|null $usageMessage = null,
array $aliases = []
){
Utils::validateCallableSignature(
fn(CommandSender $sender, Command $command, string $commandLabel, array $args) : mixed => 1,
$execute,
);
$this->execute = $execute;
parent::__construct($name, $description, $usageMessage, $aliases);
$this->setPermissions($permissions);
}
public function execute(CommandSender $sender, string $commandLabel, array $args){
return ($this->execute)($sender, $this, $commandLabel, $args);
}
}

View File

@ -26,28 +26,28 @@ namespace pocketmine\command\defaults;
use pocketmine\command\Command; use pocketmine\command\Command;
use pocketmine\command\CommandSender; use pocketmine\command\CommandSender;
use pocketmine\command\utils\InvalidCommandSyntaxException; use pocketmine\command\utils\InvalidCommandSyntaxException;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\KnownTranslationFactory;
use pocketmine\permission\DefaultPermissionNames; use pocketmine\permission\DefaultPermissionNames;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\scheduler\BulkCurlTask; use pocketmine\scheduler\BulkCurlTask;
use pocketmine\scheduler\BulkCurlTaskOperation; use pocketmine\scheduler\BulkCurlTaskOperation;
use pocketmine\timings\TimingsHandler; use pocketmine\timings\TimingsHandler;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\InternetException; use pocketmine\utils\InternetException;
use pocketmine\utils\InternetRequestResult; use pocketmine\utils\InternetRequestResult;
use pocketmine\utils\Utils;
use pocketmine\YmlServerProperties; use pocketmine\YmlServerProperties;
use Symfony\Component\Filesystem\Path; use Symfony\Component\Filesystem\Path;
use function count; use function count;
use function fclose; use function fclose;
use function file_exists; use function file_exists;
use function fopen; use function fopen;
use function fseek;
use function fwrite; use function fwrite;
use function http_build_query; use function http_build_query;
use function implode;
use function is_array; use function is_array;
use function json_decode; use function json_decode;
use function mkdir; use function mkdir;
use function stream_get_contents;
use function strtolower; use function strtolower;
use const CURLOPT_AUTOREFERER; use const CURLOPT_AUTOREFERER;
use const CURLOPT_FOLLOWLOCATION; use const CURLOPT_FOLLOWLOCATION;
@ -101,10 +101,24 @@ class TimingsCommand extends VanillaCommand{
TimingsHandler::reload(); TimingsHandler::reload();
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset()); Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset());
}elseif($mode === "merged" || $mode === "report" || $paste){ }elseif($mode === "merged" || $mode === "report" || $paste){
$timings = ""; $timingsPromise = TimingsHandler::requestPrintTimings();
if($paste){ Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect());
$fileTimings = Utils::assumeNotFalse(fopen("php://temp", "r+b"), "Opening php://temp should never fail"); $timingsPromise->onCompletion(
fn(array $lines) => $paste ? $this->uploadReport($lines, $sender) : $this->createReportFile($lines, $sender),
fn() => throw new AssumptionFailedError("This promise is not expected to be rejected")
);
}else{ }else{
throw new InvalidCommandSyntaxException();
}
return true;
}
/**
* @param string[] $lines
* @phpstan-param list<string> $lines
*/
private function createReportFile(array $lines, CommandSender $sender) : void{
$index = 0; $index = 0;
$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings"); $timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");
@ -116,20 +130,24 @@ class TimingsCommand extends VanillaCommand{
$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt"); $timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
} }
$fileTimings = fopen($timings, "a+b"); $fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b"));
}
$lines = TimingsHandler::printTimings();
foreach($lines as $line){ foreach($lines as $line){
fwrite($fileTimings, $line . PHP_EOL); fwrite($fileTimings, $line . PHP_EOL);
} }
fclose($fileTimings);
if($paste){ Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
fseek($fileTimings, 0); }
/**
* @param string[] $lines
* @phpstan-param list<string> $lines
*/
private function uploadReport(array $lines, CommandSender $sender) : void{
$data = [ $data = [
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(), "browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
"data" => $content = stream_get_contents($fileTimings) "data" => implode("\n", $lines)
]; ];
fclose($fileTimings);
$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io"); $host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
@ -169,14 +187,5 @@ class TimingsCommand extends VanillaCommand{
} }
} }
)); ));
}else{
fclose($fileTimings);
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
}
}else{
throw new InvalidCommandSyntaxException();
}
return true;
} }
} }

View File

@ -45,6 +45,7 @@ final class BedrockDataFiles{
public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json'; public const ITEM_TAGS_JSON = BEDROCK_DATA_PATH . '/item_tags.json';
public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json'; public const LEVEL_SOUND_ID_MAP_JSON = BEDROCK_DATA_PATH . '/level_sound_id_map.json';
public const PARTICLE_ID_MAP_JSON = BEDROCK_DATA_PATH . '/particle_id_map.json'; public const PARTICLE_ID_MAP_JSON = BEDROCK_DATA_PATH . '/particle_id_map.json';
public const PROTOCOL_INFO_JSON = BEDROCK_DATA_PATH . '/protocol_info.json';
public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin'; public const R12_TO_CURRENT_BLOCK_MAP_BIN = BEDROCK_DATA_PATH . '/r12_to_current_block_map.bin';
public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json'; public const R16_TO_CURRENT_ITEM_MAP_JSON = BEDROCK_DATA_PATH . '/r16_to_current_item_map.json';
public const REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json'; public const REQUIRED_ITEM_LIST_JSON = BEDROCK_DATA_PATH . '/required_item_list.json';

View File

@ -122,4 +122,5 @@ final class BiomeIds{
public const DEEP_DARK = 190; public const DEEP_DARK = 190;
public const MANGROVE_SWAMP = 191; public const MANGROVE_SWAMP = 191;
public const CHERRY_GROVE = 192; public const CHERRY_GROVE = 192;
public const PALE_GARDEN = 193;
} }

View File

@ -66,5 +66,7 @@ final class EnchantmentIdMap{
$this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING()); $this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING());
$this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK()); $this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK());
$this->register(EnchantmentIds::FROST_WALKER, VanillaEnchantments::FROST_WALKER());
} }
} }

View File

@ -93,12 +93,17 @@ final class BlockStateNames{
public const MC_VERTICAL_HALF = "minecraft:vertical_half"; public const MC_VERTICAL_HALF = "minecraft:vertical_half";
public const MOISTURIZED_AMOUNT = "moisturized_amount"; public const MOISTURIZED_AMOUNT = "moisturized_amount";
public const MULTI_FACE_DIRECTION_BITS = "multi_face_direction_bits"; public const MULTI_FACE_DIRECTION_BITS = "multi_face_direction_bits";
public const NATURAL = "natural";
public const OCCUPIED_BIT = "occupied_bit"; public const OCCUPIED_BIT = "occupied_bit";
public const OMINOUS = "ominous"; public const OMINOUS = "ominous";
public const OPEN_BIT = "open_bit"; public const OPEN_BIT = "open_bit";
public const ORIENTATION = "orientation"; public const ORIENTATION = "orientation";
public const OUTPUT_LIT_BIT = "output_lit_bit"; public const OUTPUT_LIT_BIT = "output_lit_bit";
public const OUTPUT_SUBTRACT_BIT = "output_subtract_bit"; public const OUTPUT_SUBTRACT_BIT = "output_subtract_bit";
public const PALE_MOSS_CARPET_SIDE_EAST = "pale_moss_carpet_side_east";
public const PALE_MOSS_CARPET_SIDE_NORTH = "pale_moss_carpet_side_north";
public const PALE_MOSS_CARPET_SIDE_SOUTH = "pale_moss_carpet_side_south";
public const PALE_MOSS_CARPET_SIDE_WEST = "pale_moss_carpet_side_west";
public const PERSISTENT_BIT = "persistent_bit"; public const PERSISTENT_BIT = "persistent_bit";
public const PILLAR_AXIS = "pillar_axis"; public const PILLAR_AXIS = "pillar_axis";
public const PORTAL_AXIS = "portal_axis"; public const PORTAL_AXIS = "portal_axis";
@ -116,6 +121,7 @@ final class BlockStateNames{
public const STABILITY_CHECK = "stability_check"; public const STABILITY_CHECK = "stability_check";
public const STRUCTURE_BLOCK_TYPE = "structure_block_type"; public const STRUCTURE_BLOCK_TYPE = "structure_block_type";
public const SUSPENDED_BIT = "suspended_bit"; public const SUSPENDED_BIT = "suspended_bit";
public const TIP = "tip";
public const TOGGLE_BIT = "toggle_bit"; public const TOGGLE_BIT = "toggle_bit";
public const TORCH_FACING_DIRECTION = "torch_facing_direction"; public const TORCH_FACING_DIRECTION = "torch_facing_direction";
public const TRIAL_SPAWNER_STATE = "trial_spawner_state"; public const TRIAL_SPAWNER_STATE = "trial_spawner_state";

View File

@ -106,6 +106,22 @@ final class BlockStateStringValues{
public const ORIENTATION_UP_WEST = "up_west"; public const ORIENTATION_UP_WEST = "up_west";
public const ORIENTATION_WEST_UP = "west_up"; public const ORIENTATION_WEST_UP = "west_up";
public const PALE_MOSS_CARPET_SIDE_EAST_NONE = "none";
public const PALE_MOSS_CARPET_SIDE_EAST_SHORT = "short";
public const PALE_MOSS_CARPET_SIDE_EAST_TALL = "tall";
public const PALE_MOSS_CARPET_SIDE_NORTH_NONE = "none";
public const PALE_MOSS_CARPET_SIDE_NORTH_SHORT = "short";
public const PALE_MOSS_CARPET_SIDE_NORTH_TALL = "tall";
public const PALE_MOSS_CARPET_SIDE_SOUTH_NONE = "none";
public const PALE_MOSS_CARPET_SIDE_SOUTH_SHORT = "short";
public const PALE_MOSS_CARPET_SIDE_SOUTH_TALL = "tall";
public const PALE_MOSS_CARPET_SIDE_WEST_NONE = "none";
public const PALE_MOSS_CARPET_SIDE_WEST_SHORT = "short";
public const PALE_MOSS_CARPET_SIDE_WEST_TALL = "tall";
public const PILLAR_AXIS_X = "x"; public const PILLAR_AXIS_X = "x";
public const PILLAR_AXIS_Y = "y"; public const PILLAR_AXIS_Y = "y";
public const PILLAR_AXIS_Z = "z"; public const PILLAR_AXIS_Z = "z";

View File

@ -192,6 +192,7 @@ final class BlockTypeNames{
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 = "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 CHEMICAL_HEAT = "minecraft:chemical_heat"; public const CHEMICAL_HEAT = "minecraft:chemical_heat";
public const CHERRY_BUTTON = "minecraft:cherry_button"; public const CHERRY_BUTTON = "minecraft:cherry_button";
public const CHERRY_DOOR = "minecraft:cherry_door"; public const CHERRY_DOOR = "minecraft:cherry_door";
@ -219,6 +220,7 @@ final class BlockTypeNames{
public const CHISELED_POLISHED_BLACKSTONE = "minecraft:chiseled_polished_blackstone"; public const CHISELED_POLISHED_BLACKSTONE = "minecraft:chiseled_polished_blackstone";
public const CHISELED_QUARTZ_BLOCK = "minecraft:chiseled_quartz_block"; public const CHISELED_QUARTZ_BLOCK = "minecraft:chiseled_quartz_block";
public const CHISELED_RED_SANDSTONE = "minecraft:chiseled_red_sandstone"; public const CHISELED_RED_SANDSTONE = "minecraft:chiseled_red_sandstone";
public const CHISELED_RESIN_BRICKS = "minecraft:chiseled_resin_bricks";
public const CHISELED_SANDSTONE = "minecraft:chiseled_sandstone"; public const CHISELED_SANDSTONE = "minecraft:chiseled_sandstone";
public const CHISELED_STONE_BRICKS = "minecraft:chiseled_stone_bricks"; public const CHISELED_STONE_BRICKS = "minecraft:chiseled_stone_bricks";
public const CHISELED_TUFF = "minecraft:chiseled_tuff"; public const CHISELED_TUFF = "minecraft:chiseled_tuff";
@ -227,6 +229,7 @@ final class BlockTypeNames{
public const CHORUS_PLANT = "minecraft:chorus_plant"; public const CHORUS_PLANT = "minecraft:chorus_plant";
public const CLAY = "minecraft:clay"; public const CLAY = "minecraft:clay";
public const CLIENT_REQUEST_PLACEHOLDER_BLOCK = "minecraft:client_request_placeholder_block"; public const CLIENT_REQUEST_PLACEHOLDER_BLOCK = "minecraft:client_request_placeholder_block";
public const CLOSED_EYEBLOSSOM = "minecraft:closed_eyeblossom";
public const COAL_BLOCK = "minecraft:coal_block"; public const COAL_BLOCK = "minecraft:coal_block";
public const COAL_ORE = "minecraft:coal_ore"; public const COAL_ORE = "minecraft:coal_ore";
public const COARSE_DIRT = "minecraft:coarse_dirt"; public const COARSE_DIRT = "minecraft:coarse_dirt";
@ -262,6 +265,7 @@ final class BlockTypeNames{
public const CRACKED_STONE_BRICKS = "minecraft:cracked_stone_bricks"; public const CRACKED_STONE_BRICKS = "minecraft:cracked_stone_bricks";
public const CRAFTER = "minecraft:crafter"; public const CRAFTER = "minecraft:crafter";
public const CRAFTING_TABLE = "minecraft:crafting_table"; public const CRAFTING_TABLE = "minecraft:crafting_table";
public const CREAKING_HEART = "minecraft:creaking_heart";
public const CREEPER_HEAD = "minecraft:creeper_head"; public const CREEPER_HEAD = "minecraft:creeper_head";
public const CRIMSON_BUTTON = "minecraft:crimson_button"; public const CRIMSON_BUTTON = "minecraft:crimson_button";
public const CRIMSON_DOOR = "minecraft:crimson_door"; public const CRIMSON_DOOR = "minecraft:crimson_door";
@ -831,6 +835,7 @@ final class BlockTypeNames{
public const OBSERVER = "minecraft:observer"; public const OBSERVER = "minecraft:observer";
public const OBSIDIAN = "minecraft:obsidian"; public const OBSIDIAN = "minecraft:obsidian";
public const OCHRE_FROGLIGHT = "minecraft:ochre_froglight"; public const OCHRE_FROGLIGHT = "minecraft:ochre_froglight";
public const OPEN_EYEBLOSSOM = "minecraft:open_eyeblossom";
public const ORANGE_CANDLE = "minecraft:orange_candle"; public const ORANGE_CANDLE = "minecraft:orange_candle";
public const ORANGE_CANDLE_CAKE = "minecraft:orange_candle_cake"; public const ORANGE_CANDLE_CAKE = "minecraft:orange_candle_cake";
public const ORANGE_CARPET = "minecraft:orange_carpet"; public const ORANGE_CARPET = "minecraft:orange_carpet";
@ -856,6 +861,26 @@ final class BlockTypeNames{
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 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_MOSS_BLOCK = "minecraft:pale_moss_block";
public const PALE_MOSS_CARPET = "minecraft:pale_moss_carpet";
public const PALE_OAK_BUTTON = "minecraft:pale_oak_button";
public const PALE_OAK_DOOR = "minecraft:pale_oak_door";
public const PALE_OAK_DOUBLE_SLAB = "minecraft:pale_oak_double_slab";
public const PALE_OAK_FENCE = "minecraft:pale_oak_fence";
public const PALE_OAK_FENCE_GATE = "minecraft:pale_oak_fence_gate";
public const PALE_OAK_HANGING_SIGN = "minecraft:pale_oak_hanging_sign";
public const PALE_OAK_LEAVES = "minecraft:pale_oak_leaves";
public const PALE_OAK_LOG = "minecraft:pale_oak_log";
public const PALE_OAK_PLANKS = "minecraft:pale_oak_planks";
public const PALE_OAK_PRESSURE_PLATE = "minecraft:pale_oak_pressure_plate";
public const PALE_OAK_SAPLING = "minecraft:pale_oak_sapling";
public const PALE_OAK_SLAB = "minecraft:pale_oak_slab";
public const PALE_OAK_STAIRS = "minecraft:pale_oak_stairs";
public const PALE_OAK_STANDING_SIGN = "minecraft:pale_oak_standing_sign";
public const PALE_OAK_TRAPDOOR = "minecraft:pale_oak_trapdoor";
public const PALE_OAK_WALL_SIGN = "minecraft:pale_oak_wall_sign";
public const PALE_OAK_WOOD = "minecraft:pale_oak_wood";
public const PEARLESCENT_FROGLIGHT = "minecraft:pearlescent_froglight"; public const PEARLESCENT_FROGLIGHT = "minecraft:pearlescent_froglight";
public const PEONY = "minecraft:peony"; public const PEONY = "minecraft:peony";
public const PETRIFIED_OAK_DOUBLE_SLAB = "minecraft:petrified_oak_double_slab"; public const PETRIFIED_OAK_DOUBLE_SLAB = "minecraft:petrified_oak_double_slab";
@ -994,6 +1019,13 @@ final class BlockTypeNames{
public const REINFORCED_DEEPSLATE = "minecraft:reinforced_deepslate"; public const REINFORCED_DEEPSLATE = "minecraft:reinforced_deepslate";
public const REPEATING_COMMAND_BLOCK = "minecraft:repeating_command_block"; public const REPEATING_COMMAND_BLOCK = "minecraft:repeating_command_block";
public const RESERVED6 = "minecraft:reserved6"; public const RESERVED6 = "minecraft:reserved6";
public const RESIN_BLOCK = "minecraft:resin_block";
public const RESIN_BRICK_DOUBLE_SLAB = "minecraft:resin_brick_double_slab";
public const RESIN_BRICK_SLAB = "minecraft:resin_brick_slab";
public const RESIN_BRICK_STAIRS = "minecraft:resin_brick_stairs";
public const RESIN_BRICK_WALL = "minecraft:resin_brick_wall";
public const RESIN_BRICKS = "minecraft:resin_bricks";
public const RESIN_CLUMP = "minecraft:resin_clump";
public const RESPAWN_ANCHOR = "minecraft:respawn_anchor"; public const RESPAWN_ANCHOR = "minecraft:respawn_anchor";
public const ROSE_BUSH = "minecraft:rose_bush"; public const ROSE_BUSH = "minecraft:rose_bush";
public const SAND = "minecraft:sand"; public const SAND = "minecraft:sand";
@ -1096,6 +1128,8 @@ final class BlockTypeNames{
public const STRIPPED_MANGROVE_WOOD = "minecraft:stripped_mangrove_wood"; public const STRIPPED_MANGROVE_WOOD = "minecraft:stripped_mangrove_wood";
public const STRIPPED_OAK_LOG = "minecraft:stripped_oak_log"; public const STRIPPED_OAK_LOG = "minecraft:stripped_oak_log";
public const STRIPPED_OAK_WOOD = "minecraft:stripped_oak_wood"; public const STRIPPED_OAK_WOOD = "minecraft:stripped_oak_wood";
public const STRIPPED_PALE_OAK_LOG = "minecraft:stripped_pale_oak_log";
public const STRIPPED_PALE_OAK_WOOD = "minecraft:stripped_pale_oak_wood";
public const STRIPPED_SPRUCE_LOG = "minecraft:stripped_spruce_log"; public const STRIPPED_SPRUCE_LOG = "minecraft:stripped_spruce_log";
public const STRIPPED_SPRUCE_WOOD = "minecraft:stripped_spruce_wood"; public const STRIPPED_SPRUCE_WOOD = "minecraft:stripped_spruce_wood";
public const STRIPPED_WARPED_HYPHAE = "minecraft:stripped_warped_hyphae"; public const STRIPPED_WARPED_HYPHAE = "minecraft:stripped_warped_hyphae";

View File

@ -352,6 +352,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER()); $this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER());
$this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD()); $this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD());
$this->map1to1Item(Ids::RAW_IRON, Items::RAW_IRON()); $this->map1to1Item(Ids::RAW_IRON, Items::RAW_IRON());
$this->map1to1Item(Ids::RECOVERY_COMPASS, Items::RECOVERY_COMPASS());
$this->map1to1Item(Ids::REDSTONE, Items::REDSTONE_DUST()); $this->map1to1Item(Ids::REDSTONE, Items::REDSTONE_DUST());
$this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); $this->map1to1Item(Ids::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
$this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH()); $this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH());

View File

@ -75,6 +75,7 @@ final class ItemTypeNames{
public const BLEACH = "minecraft:bleach"; public const BLEACH = "minecraft:bleach";
public const BLUE_BUNDLE = "minecraft:blue_bundle"; public const BLUE_BUNDLE = "minecraft:blue_bundle";
public const BLUE_DYE = "minecraft:blue_dye"; public const BLUE_DYE = "minecraft:blue_dye";
public const BOARD = "minecraft:board";
public const BOAT = "minecraft:boat"; public const BOAT = "minecraft:boat";
public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg"; public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg";
public const BOLT_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:bolt_armor_trim_smithing_template"; public const BOLT_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:bolt_armor_trim_smithing_template";
@ -154,6 +155,7 @@ final class ItemTypeNames{
public const CORAL_FAN = "minecraft:coral_fan"; public const CORAL_FAN = "minecraft:coral_fan";
public const CORAL_FAN_DEAD = "minecraft:coral_fan_dead"; public const CORAL_FAN_DEAD = "minecraft:coral_fan_dead";
public const COW_SPAWN_EGG = "minecraft:cow_spawn_egg"; public const COW_SPAWN_EGG = "minecraft:cow_spawn_egg";
public const CREAKING_SPAWN_EGG = "minecraft:creaking_spawn_egg";
public const CREEPER_BANNER_PATTERN = "minecraft:creeper_banner_pattern"; public const CREEPER_BANNER_PATTERN = "minecraft:creeper_banner_pattern";
public const CREEPER_SPAWN_EGG = "minecraft:creeper_spawn_egg"; public const CREEPER_SPAWN_EGG = "minecraft:creeper_spawn_egg";
public const CRIMSON_DOOR = "minecraft:crimson_door"; public const CRIMSON_DOOR = "minecraft:crimson_door";
@ -398,6 +400,11 @@ final class ItemTypeNames{
public const ORANGE_DYE = "minecraft:orange_dye"; public const ORANGE_DYE = "minecraft:orange_dye";
public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door"; public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door";
public const PAINTING = "minecraft:painting"; public const PAINTING = "minecraft:painting";
public const PALE_OAK_BOAT = "minecraft:pale_oak_boat";
public const PALE_OAK_CHEST_BOAT = "minecraft:pale_oak_chest_boat";
public const PALE_OAK_DOOR = "minecraft:pale_oak_door";
public const PALE_OAK_HANGING_SIGN = "minecraft:pale_oak_hanging_sign";
public const PALE_OAK_SIGN = "minecraft:pale_oak_sign";
public const PANDA_SPAWN_EGG = "minecraft:panda_spawn_egg"; public const PANDA_SPAWN_EGG = "minecraft:panda_spawn_egg";
public const PAPER = "minecraft:paper"; public const PAPER = "minecraft:paper";
public const PARROT_SPAWN_EGG = "minecraft:parrot_spawn_egg"; public const PARROT_SPAWN_EGG = "minecraft:parrot_spawn_egg";
@ -448,6 +455,7 @@ final class ItemTypeNames{
public const RED_FLOWER = "minecraft:red_flower"; public const RED_FLOWER = "minecraft:red_flower";
public const REDSTONE = "minecraft:redstone"; public const REDSTONE = "minecraft:redstone";
public const REPEATER = "minecraft:repeater"; public const REPEATER = "minecraft:repeater";
public const RESIN_BRICK = "minecraft:resin_brick";
public const RIB_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:rib_armor_trim_smithing_template"; public const RIB_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:rib_armor_trim_smithing_template";
public const ROTTEN_FLESH = "minecraft:rotten_flesh"; public const ROTTEN_FLESH = "minecraft:rotten_flesh";
public const SADDLE = "minecraft:saddle"; public const SADDLE = "minecraft:saddle";

View File

@ -75,7 +75,6 @@ use function deg2rad;
use function floor; use function floor;
use function fmod; use function fmod;
use function get_class; use function get_class;
use function lcg_value;
use function sin; use function sin;
use function spl_object_id; use function spl_object_id;
use const M_PI_2; use const M_PI_2;
@ -910,7 +909,7 @@ abstract class Entity{
return false; return false;
} }
$force = lcg_value() * 0.2 + 0.1; $force = Utils::getRandomFloat() * 0.2 + 0.1;
$this->motion = match($direction){ $this->motion = match($direction){
Facing::WEST => $this->motion->withComponents(-$force, null, null), Facing::WEST => $this->motion->withComponents(-$force, null, null),

View File

@ -34,6 +34,7 @@ interface FoodSource extends Consumable{
/** /**
* Returns whether a Human eating this FoodSource must have a non-full hunger bar. * Returns whether a Human eating this FoodSource must have a non-full hunger bar.
* This is ignored in creative mode and in peaceful difficulty.
*/ */
public function requiresHunger() : bool; public function requiresHunger() : bool;
} }

View File

@ -67,6 +67,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket; use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\world\sound\TotemUseSound; use pocketmine\world\sound\TotemUseSound;
use pocketmine\world\World;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface; use Ramsey\Uuid\UuidInterface;
use function array_fill; use function array_fill;
@ -189,8 +190,16 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
return $this->hungerManager; return $this->hungerManager;
} }
/**
* Returns whether the Human can eat food. This may return a different result than {@link HungerManager::isHungry()},
* as HungerManager only handles the hunger bar.
*/
public function canEat() : bool{
return $this->hungerManager->isHungry() || $this->getWorld()->getDifficulty() === World::DIFFICULTY_PEACEFUL;
}
public function consumeObject(Consumable $consumable) : bool{ public function consumeObject(Consumable $consumable) : bool{
if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->hungerManager->isHungry()){ if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->canEat()){
return false; return false;
} }

View File

@ -88,7 +88,8 @@ class HungerManager{
} }
/** /**
* Returns whether this Human may consume objects requiring hunger. * Returns whether the food level is below the maximum.
* This doesn't decide if the entity can eat food. Use {@link Human::canEat()} for that.
*/ */
public function isHungry() : bool{ public function isHungry() : bool{
return $this->getFood() < $this->getMaxFood(); return $this->getFood() < $this->getMaxFood();

View File

@ -25,6 +25,8 @@ namespace pocketmine\entity;
use pocketmine\block\Block; use pocketmine\block\Block;
use pocketmine\block\BlockTypeIds; use pocketmine\block\BlockTypeIds;
use pocketmine\block\VanillaBlocks;
use pocketmine\block\Water;
use pocketmine\data\bedrock\EffectIdMap; use pocketmine\data\bedrock\EffectIdMap;
use pocketmine\entity\animation\DeathAnimation; use pocketmine\entity\animation\DeathAnimation;
use pocketmine\entity\animation\HurtAnimation; use pocketmine\entity\animation\HurtAnimation;
@ -44,6 +46,7 @@ use pocketmine\item\Durable;
use pocketmine\item\enchantment\Enchantment; use pocketmine\item\enchantment\Enchantment;
use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\math\VoxelRayTrace; use pocketmine\math\VoxelRayTrace;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
@ -58,18 +61,19 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\timings\Timings; use pocketmine\timings\Timings;
use pocketmine\utils\Binary; use pocketmine\utils\Binary;
use pocketmine\utils\Utils;
use pocketmine\world\sound\BurpSound; use pocketmine\world\sound\BurpSound;
use pocketmine\world\sound\EntityLandSound; use pocketmine\world\sound\EntityLandSound;
use pocketmine\world\sound\EntityLongFallSound; use pocketmine\world\sound\EntityLongFallSound;
use pocketmine\world\sound\EntityShortFallSound; use pocketmine\world\sound\EntityShortFallSound;
use pocketmine\world\sound\ItemBreakSound; use pocketmine\world\sound\ItemBreakSound;
use function abs;
use function array_shift; use function array_shift;
use function atan2; use function atan2;
use function ceil; use function ceil;
use function count; use function count;
use function floor; use function floor;
use function ksort; use function ksort;
use function lcg_value;
use function max; use function max;
use function min; use function min;
use function mt_getrandmax; use function mt_getrandmax;
@ -128,6 +132,8 @@ abstract class Living extends Entity{
protected bool $gliding = false; protected bool $gliding = false;
protected bool $swimming = false; protected bool $swimming = false;
private ?int $frostWalkerLevel = null;
protected function getInitialDragMultiplier() : float{ return 0.02; } protected function getInitialDragMultiplier() : float{ return 0.02; }
protected function getInitialGravity() : float{ return 0.08; } protected function getInitialGravity() : float{ return 0.08; }
@ -151,6 +157,14 @@ abstract class Living extends Entity{
$this->getViewers(), $this->getViewers(),
fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this) fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this)
))); )));
$this->armorInventory->getListeners()->add(new CallbackInventoryListener(
onSlotChange: function(Inventory $inventory, int $slot) : void{
if($slot === ArmorInventory::SLOT_FEET){
$this->frostWalkerLevel = null;
}
},
onContentChange: function() : void{ $this->frostWalkerLevel = null; }
));
$health = $this->getMaxHealth(); $health = $this->getMaxHealth();
@ -490,7 +504,7 @@ abstract class Living extends Entity{
$helmet = $this->armorInventory->getHelmet(); $helmet = $this->armorInventory->getHelmet();
if($helmet instanceof Armor){ if($helmet instanceof Armor){
$finalDamage = $source->getFinalDamage(); $finalDamage = $source->getFinalDamage();
$this->damageItem($helmet, (int) round($finalDamage * 4 + lcg_value() * $finalDamage * 2)); $this->damageItem($helmet, (int) round($finalDamage * 4 + Utils::getRandomFloat() * $finalDamage * 2));
$this->armorInventory->setHelmet($helmet); $this->armorInventory->setHelmet($helmet);
} }
} }
@ -687,6 +701,47 @@ abstract class Living extends Entity{
return $hasUpdate; return $hasUpdate;
} }
protected function move(float $dx, float $dy, float $dz) : void{
$oldX = $this->location->x;
$oldZ = $this->location->z;
parent::move($dx, $dy, $dz);
$frostWalkerLevel = $this->getFrostWalkerLevel();
if($frostWalkerLevel > 0 && (abs($this->location->x - $oldX) > self::MOTION_THRESHOLD || abs($this->location->z - $oldZ) > self::MOTION_THRESHOLD)){
$this->applyFrostWalker($frostWalkerLevel);
}
}
protected function applyFrostWalker(int $level) : void{
$radius = $level + 2;
$world = $this->getWorld();
$baseX = $this->location->getFloorX();
$y = $this->location->getFloorY() - 1;
$baseZ = $this->location->getFloorZ();
$frostedIce = VanillaBlocks::FROSTED_ICE();
for($x = $baseX - $radius; $x <= $baseX + $radius; $x++){
for($z = $baseZ - $radius; $z <= $baseZ + $radius; $z++){
$block = $world->getBlockAt($x, $y, $z);
if(
!$block instanceof Water ||
!$block->isSource() ||
$world->getBlockAt($x, $y + 1, $z)->getTypeId() !== BlockTypeIds::AIR ||
count($world->getNearbyEntities(AxisAlignedBB::one()->offset($x, $y, $z))) !== 0
){
continue;
}
$world->setBlockAt($x, $y, $z, $frostedIce);
}
}
}
public function getFrostWalkerLevel() : int{
return $this->frostWalkerLevel ??= $this->armorInventory->getBoots()->getEnchantmentLevel(VanillaEnchantments::FROST_WALKER());
}
/** /**
* Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water. * Ticks the entity's air supply, consuming it when underwater and regenerating it when out of water.
*/ */
@ -697,7 +752,7 @@ abstract class Living extends Entity{
$this->setBreathing(false); $this->setBreathing(false);
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 || if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 ||
lcg_value() <= (1 / ($respirationLevel + 1)) Utils::getRandomFloat() <= (1 / ($respirationLevel + 1))
){ ){
$ticks -= $tickDiff; $ticks -= $tickDiff;
if($ticks <= -20){ if($ticks <= -20){

View File

@ -33,7 +33,7 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\IntTag;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Binary; use pocketmine\utils\Binary;
use function lcg_value; use pocketmine\utils\Utils;
use function mt_rand; use function mt_rand;
class Armor extends Durable{ class Armor extends Durable{
@ -129,7 +129,7 @@ class Armor extends Durable{
$chance = 1 / ($unbreakingLevel + 1); $chance = 1 / ($unbreakingLevel + 1);
for($i = 0; $i < $amount; ++$i){ for($i = 0; $i < $amount; ++$i){
if(mt_rand(1, 100) > 60 && lcg_value() > $chance){ //unbreaking only applies to armor 40% of the time at best if(mt_rand(1, 100) > 60 && Utils::getRandomFloat() > $chance){ //unbreaking only applies to armor 40% of the time at best
$negated++; $negated++;
} }
} }

View File

@ -25,7 +25,7 @@ namespace pocketmine\item;
use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\enchantment\VanillaEnchantments;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use function lcg_value; use pocketmine\utils\Utils;
use function min; use function min;
abstract class Durable extends Item{ abstract class Durable extends Item{
@ -87,7 +87,7 @@ abstract class Durable extends Item{
$chance = 1 / ($unbreakingLevel + 1); $chance = 1 / ($unbreakingLevel + 1);
for($i = 0; $i < $amount; ++$i){ for($i = 0; $i < $amount; ++$i){
if(lcg_value() > $chance){ if(Utils::getRandomFloat() > $chance){
$negated++; $negated++;
} }
} }

View File

@ -44,6 +44,6 @@ abstract class Food extends Item implements FoodSourceItem{
} }
public function canStartUsingItem(Player $player) : bool{ public function canStartUsingItem(Player $player) : bool{
return !$this->requiresHunger() || $player->getHungerManager()->isHungry(); return !$this->requiresHunger() || $player->canEat();
} }
} }

View File

@ -327,8 +327,9 @@ final class ItemTypeIds{
public const GOAT_HORN = 20288; public const GOAT_HORN = 20288;
public const END_CRYSTAL = 20289; public const END_CRYSTAL = 20289;
public const ICE_BOMB = 20290; public const ICE_BOMB = 20290;
public const RECOVERY_COMPASS = 20291;
public const FIRST_UNUSED_ITEM_ID = 20291; public const FIRST_UNUSED_ITEM_ID = 20292;
private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID; private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;

View File

@ -25,7 +25,7 @@ namespace pocketmine\item;
use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects; use pocketmine\entity\effect\VanillaEffects;
use function lcg_value; use pocketmine\utils\Utils;
class RottenFlesh extends Food{ class RottenFlesh extends Food{
@ -38,7 +38,7 @@ class RottenFlesh extends Food{
} }
public function getAdditionalEffects() : array{ public function getAdditionalEffects() : array{
if(lcg_value() <= 0.8){ if(Utils::getRandomFloat() <= 0.8){
return [ return [
new EffectInstance(VanillaEffects::HUNGER(), 600) new EffectInstance(VanillaEffects::HUNGER(), 600)
]; ];

View File

@ -27,15 +27,15 @@ use pocketmine\block\Block;
use pocketmine\entity\Entity; use pocketmine\entity\Entity;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\player\Player; use pocketmine\player\Player;
use pocketmine\utils\Utils;
use pocketmine\world\World; use pocketmine\world\World;
use function lcg_value;
abstract class SpawnEgg extends Item{ abstract class SpawnEgg extends Item{
abstract protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity; abstract protected function createEntity(World $world, Vector3 $pos, float $yaw, float $pitch) : Entity;
public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{ public function onInteractBlock(Player $player, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, array &$returnedItems) : ItemUseResult{
$entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), lcg_value() * 360, 0); $entity = $this->createEntity($player->getWorld(), $blockReplace->getPosition()->add(0.5, 0, 0.5), Utils::getRandomFloat() * 360, 0);
if($this->hasCustomName()){ if($this->hasCustomName()){
$entity->setNameTag($this->getCustomName()); $entity->setNameTag($this->getCustomName());

View File

@ -1481,6 +1481,7 @@ final class StringToItemParser extends StringToTParser{
$result->register("record_strad", fn() => Items::RECORD_STRAD()); $result->register("record_strad", fn() => Items::RECORD_STRAD());
$result->register("record_wait", fn() => Items::RECORD_WAIT()); $result->register("record_wait", fn() => Items::RECORD_WAIT());
$result->register("record_ward", fn() => Items::RECORD_WARD()); $result->register("record_ward", fn() => Items::RECORD_WARD());
$result->register("recovery_compass", fn() => Items::RECOVERY_COMPASS());
$result->register("redstone", fn() => Items::REDSTONE_DUST()); $result->register("redstone", fn() => Items::REDSTONE_DUST());
$result->register("redstone_dust", fn() => Items::REDSTONE_DUST()); $result->register("redstone_dust", fn() => Items::REDSTONE_DUST());
$result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE()); $result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());

View File

@ -284,6 +284,7 @@ use function strtolower;
* @method static Record RECORD_STRAD() * @method static Record RECORD_STRAD()
* @method static Record RECORD_WAIT() * @method static Record RECORD_WAIT()
* @method static Record RECORD_WARD() * @method static Record RECORD_WARD()
* @method static Item RECOVERY_COMPASS()
* @method static Redstone REDSTONE_DUST() * @method static Redstone REDSTONE_DUST()
* @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE() * @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE()
* @method static RottenFlesh ROTTEN_FLESH() * @method static RottenFlesh ROTTEN_FLESH()
@ -574,6 +575,7 @@ final class VanillaItems{
self::register("record_strad", fn(IID $id) => new Record($id, RecordType::DISK_STRAD, "Record Strad")); self::register("record_strad", fn(IID $id) => new Record($id, RecordType::DISK_STRAD, "Record Strad"));
self::register("record_wait", fn(IID $id) => new Record($id, RecordType::DISK_WAIT, "Record Wait")); self::register("record_wait", fn(IID $id) => new Record($id, RecordType::DISK_WAIT, "Record Wait"));
self::register("record_ward", fn(IID $id) => new Record($id, RecordType::DISK_WARD, "Record Ward")); self::register("record_ward", fn(IID $id) => new Record($id, RecordType::DISK_WARD, "Record Ward"));
self::register("recovery_compass", fn(IID $id) => new Item($id, "Recovery Compass"));
self::register("redstone_dust", fn(IID $id) => new Redstone($id, "Redstone")); self::register("redstone_dust", fn(IID $id) => new Redstone($id, "Redstone"));
self::register("rotten_flesh", fn(IID $id) => new RottenFlesh($id, "Rotten Flesh")); self::register("rotten_flesh", fn(IID $id) => new RottenFlesh($id, "Rotten Flesh"));
self::register("scute", fn(IID $id) => new Item($id, "Scute")); self::register("scute", fn(IID $id) => new Item($id, "Scute"));

View File

@ -57,6 +57,7 @@ final class AvailableEnchantmentRegistry{
$this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]); $this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]);
$this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []); $this->register(Enchantments::RESPIRATION(), [Tags::HELMET], []);
$this->register(Enchantments::AQUA_AFFINITY(), [Tags::HELMET], []); $this->register(Enchantments::AQUA_AFFINITY(), [Tags::HELMET], []);
$this->register(Enchantments::FROST_WALKER(), [/* no primary items */], [Tags::BOOTS]);
$this->register(Enchantments::SHARPNESS(), [Tags::SWORD, Tags::AXE], []); $this->register(Enchantments::SHARPNESS(), [Tags::SWORD, Tags::AXE], []);
$this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []); $this->register(Enchantments::KNOCKBACK(), [Tags::SWORD], []);
$this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []); $this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []);

View File

@ -44,6 +44,7 @@ final class StringToEnchantmentParser extends StringToTParser{
$result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION()); $result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION());
$result->register("flame", fn() => VanillaEnchantments::FLAME()); $result->register("flame", fn() => VanillaEnchantments::FLAME());
$result->register("fortune", fn() => VanillaEnchantments::FORTUNE()); $result->register("fortune", fn() => VanillaEnchantments::FORTUNE());
$result->register("frost_walker", fn() => VanillaEnchantments::FROST_WALKER());
$result->register("infinity", fn() => VanillaEnchantments::INFINITY()); $result->register("infinity", fn() => VanillaEnchantments::INFINITY());
$result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK()); $result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK());
$result->register("mending", fn() => VanillaEnchantments::MENDING()); $result->register("mending", fn() => VanillaEnchantments::MENDING());

View File

@ -41,6 +41,7 @@ use pocketmine\utils\RegistryTrait;
* @method static ProtectionEnchantment FIRE_PROTECTION() * @method static ProtectionEnchantment FIRE_PROTECTION()
* @method static Enchantment FLAME() * @method static Enchantment FLAME()
* @method static Enchantment FORTUNE() * @method static Enchantment FORTUNE()
* @method static Enchantment FROST_WALKER()
* @method static Enchantment INFINITY() * @method static Enchantment INFINITY()
* @method static KnockbackEnchantment KNOCKBACK() * @method static KnockbackEnchantment KNOCKBACK()
* @method static Enchantment MENDING() * @method static Enchantment MENDING()
@ -131,6 +132,14 @@ final class VanillaEnchantments{
fn(int $level) : int => 10 * $level, fn(int $level) : int => 10 * $level,
30 30
)); ));
self::register("FROST_WALKER", new Enchantment(
KnownTranslationFactory::enchantment_frostwalker(),
Rarity::RARE,
2,
fn(int $level) : int => 10 * $level,
15
));
self::register("AQUA_AFFINITY", new Enchantment( self::register("AQUA_AFFINITY", new Enchantment(
KnownTranslationFactory::enchantment_waterWorker(), KnownTranslationFactory::enchantment_waterWorker(),
Rarity::RARE, Rarity::RARE,

View File

@ -1441,6 +1441,10 @@ final class KnownTranslationFactory{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED, []); return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED, []);
} }
public static function pocketmine_command_timings_collect() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_COLLECT, []);
}
public static function pocketmine_command_timings_description() : Translatable{ public static function pocketmine_command_timings_description() : Translatable{
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_DESCRIPTION, []); return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_DESCRIPTION, []);
} }

View File

@ -315,6 +315,7 @@ final class KnownTranslationKeys{
public const POCKETMINE_COMMAND_TIME_DESCRIPTION = "pocketmine.command.time.description"; public const POCKETMINE_COMMAND_TIME_DESCRIPTION = "pocketmine.command.time.description";
public const POCKETMINE_COMMAND_TIME_USAGE = "pocketmine.command.time.usage"; public const POCKETMINE_COMMAND_TIME_USAGE = "pocketmine.command.time.usage";
public const POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED = "pocketmine.command.timings.alreadyEnabled"; public const POCKETMINE_COMMAND_TIMINGS_ALREADYENABLED = "pocketmine.command.timings.alreadyEnabled";
public const POCKETMINE_COMMAND_TIMINGS_COLLECT = "pocketmine.command.timings.collect";
public const POCKETMINE_COMMAND_TIMINGS_DESCRIPTION = "pocketmine.command.timings.description"; public const POCKETMINE_COMMAND_TIMINGS_DESCRIPTION = "pocketmine.command.timings.description";
public const POCKETMINE_COMMAND_TIMINGS_DISABLE = "pocketmine.command.timings.disable"; public const POCKETMINE_COMMAND_TIMINGS_DISABLE = "pocketmine.command.timings.disable";
public const POCKETMINE_COMMAND_TIMINGS_ENABLE = "pocketmine.command.timings.enable"; public const POCKETMINE_COMMAND_TIMINGS_ENABLE = "pocketmine.command.timings.enable";

View File

@ -77,6 +77,7 @@ use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
use pocketmine\network\mcpe\protocol\PlayerInputPacket; use pocketmine\network\mcpe\protocol\PlayerInputPacket;
use pocketmine\network\mcpe\protocol\PlayerSkinPacket; use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
use pocketmine\network\mcpe\protocol\serializer\BitSet;
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket; use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
use pocketmine\network\mcpe\protocol\SetActorMotionPacket; use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket; use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
@ -135,7 +136,7 @@ class InGamePacketHandler extends PacketHandler{
protected ?Vector3 $lastPlayerAuthInputPosition = null; protected ?Vector3 $lastPlayerAuthInputPosition = null;
protected ?float $lastPlayerAuthInputYaw = null; protected ?float $lastPlayerAuthInputYaw = null;
protected ?float $lastPlayerAuthInputPitch = null; protected ?float $lastPlayerAuthInputPitch = null;
protected ?int $lastPlayerAuthInputFlags = null; protected ?BitSet $lastPlayerAuthInputFlags = null;
public bool $forceMoveSync = false; public bool $forceMoveSync = false;
@ -161,9 +162,9 @@ class InGamePacketHandler extends PacketHandler{
return true; return true;
} }
private function resolveOnOffInputFlags(int $inputFlags, int $startFlag, int $stopFlag) : ?bool{ private function resolveOnOffInputFlags(BitSet $inputFlags, int $startFlag, int $stopFlag) : ?bool{
$enabled = ($inputFlags & (1 << $startFlag)) !== 0; $enabled = $inputFlags->get($startFlag);
$disabled = ($inputFlags & (1 << $stopFlag)) !== 0; $disabled = $inputFlags->get($stopFlag);
if($enabled !== $disabled){ if($enabled !== $disabled){
return $enabled; return $enabled;
} }
@ -215,7 +216,10 @@ class InGamePacketHandler extends PacketHandler{
if($inputFlags !== $this->lastPlayerAuthInputFlags){ if($inputFlags !== $this->lastPlayerAuthInputFlags){
$this->lastPlayerAuthInputFlags = $inputFlags; $this->lastPlayerAuthInputFlags = $inputFlags;
$sneaking = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SNEAKING, PlayerAuthInputFlags::STOP_SNEAKING); $sneaking = $inputFlags->get(PlayerAuthInputFlags::SNEAKING);
if($this->player->isSneaking() === $sneaking){
$sneaking = null;
}
$sprinting = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SPRINTING, PlayerAuthInputFlags::STOP_SPRINTING); $sprinting = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SPRINTING, PlayerAuthInputFlags::STOP_SPRINTING);
$swimming = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SWIMMING, PlayerAuthInputFlags::STOP_SWIMMING); $swimming = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SWIMMING, PlayerAuthInputFlags::STOP_SWIMMING);
$gliding = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_GLIDING, PlayerAuthInputFlags::STOP_GLIDING); $gliding = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_GLIDING, PlayerAuthInputFlags::STOP_GLIDING);
@ -230,10 +234,10 @@ class InGamePacketHandler extends PacketHandler{
$this->player->sendData([$this->player]); $this->player->sendData([$this->player]);
} }
if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){ if($inputFlags->get(PlayerAuthInputFlags::START_JUMPING)){
$this->player->jump(); $this->player->jump();
} }
if($packet->hasFlag(PlayerAuthInputFlags::MISSED_SWING)){ if($inputFlags->get(PlayerAuthInputFlags::MISSED_SWING)){
$this->player->missSwing(); $this->player->missSwing();
} }
} }

View File

@ -94,6 +94,7 @@ final class ItemStackResponseBuilder{
$item->getCount(), $item->getCount(),
$itemStackInfo->getStackId(), $itemStackInfo->getStackId(),
$item->getCustomName(), $item->getCustomName(),
$item->getCustomName(),
$item instanceof Durable ? $item->getDamage() : 0, $item instanceof Durable ? $item->getDamage() : 0,
); );
} }

View File

@ -37,6 +37,7 @@ use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackStackEntry;
use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType;
use pocketmine\resourcepacks\ResourcePack; use pocketmine\resourcepacks\ResourcePack;
use Ramsey\Uuid\Uuid;
use function array_keys; use function array_keys;
use function array_map; use function array_map;
use function ceil; use function ceil;
@ -103,7 +104,7 @@ class ResourcePacksPacketHandler extends PacketHandler{
//TODO: more stuff //TODO: more stuff
return new ResourcePackInfoEntry( return new ResourcePackInfoEntry(
$pack->getPackId(), Uuid::fromString($pack->getPackId()),
$pack->getPackVersion(), $pack->getPackVersion(),
$pack->getPackSize(), $pack->getPackSize(),
$this->encryptionKeys[$pack->getPackId()] ?? "", $this->encryptionKeys[$pack->getPackId()] ?? "",
@ -117,7 +118,9 @@ class ResourcePacksPacketHandler extends PacketHandler{
resourcePackEntries: $resourcePackEntries, resourcePackEntries: $resourcePackEntries,
mustAccept: $this->mustAccept, mustAccept: $this->mustAccept,
hasAddons: false, hasAddons: false,
hasScripts: false hasScripts: false,
worldTemplateId: Uuid::fromString(Uuid::NIL),
worldTemplateVersion: ""
)); ));
$this->session->getLogger()->debug("Waiting for client to accept resource packs"); $this->session->getLogger()->debug("Waiting for client to accept resource packs");
} }

View File

@ -109,6 +109,7 @@ use pocketmine\network\mcpe\protocol\AnimatePacket;
use pocketmine\network\mcpe\protocol\MovePlayerPacket; use pocketmine\network\mcpe\protocol\MovePlayerPacket;
use pocketmine\network\mcpe\protocol\SetActorMotionPacket; use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\network\mcpe\protocol\types\BlockPosition;
use pocketmine\network\mcpe\protocol\types\DimensionIds;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags;
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
@ -190,6 +191,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
private const TAG_SPAWN_X = "SpawnX"; //TAG_Int private const TAG_SPAWN_X = "SpawnX"; //TAG_Int
private const TAG_SPAWN_Y = "SpawnY"; //TAG_Int private const TAG_SPAWN_Y = "SpawnY"; //TAG_Int
private const TAG_SPAWN_Z = "SpawnZ"; //TAG_Int private const TAG_SPAWN_Z = "SpawnZ"; //TAG_Int
private const TAG_DEATH_WORLD = "DeathLevel"; //TAG_String
private const TAG_DEATH_X = "DeathPositionX"; //TAG_Int
private const TAG_DEATH_Y = "DeathPositionY"; //TAG_Int
private const TAG_DEATH_Z = "DeathPositionZ"; //TAG_Int
public const TAG_LEVEL = "Level"; //TAG_String public const TAG_LEVEL = "Level"; //TAG_String
public const TAG_LAST_KNOWN_XUID = "LastKnownXUID"; //TAG_String public const TAG_LAST_KNOWN_XUID = "LastKnownXUID"; //TAG_String
@ -272,6 +277,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
private bool $respawnLocked = false; private bool $respawnLocked = false;
private ?Position $deathPosition = null;
//TODO: Abilities //TODO: Abilities
protected bool $autoJump = true; protected bool $autoJump = true;
protected bool $allowFlight = false; protected bool $allowFlight = false;
@ -394,6 +401,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_SPAWN_WORLD, ""))) instanceof World){ if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_SPAWN_WORLD, ""))) instanceof World){
$this->spawnPosition = new Position($nbt->getInt(self::TAG_SPAWN_X), $nbt->getInt(self::TAG_SPAWN_Y), $nbt->getInt(self::TAG_SPAWN_Z), $world); $this->spawnPosition = new Position($nbt->getInt(self::TAG_SPAWN_X), $nbt->getInt(self::TAG_SPAWN_Y), $nbt->getInt(self::TAG_SPAWN_Z), $world);
} }
if(($world = $this->server->getWorldManager()->getWorldByName($nbt->getString(self::TAG_DEATH_WORLD, ""))) instanceof World){
$this->deathPosition = new Position($nbt->getInt(self::TAG_DEATH_X), $nbt->getInt(self::TAG_DEATH_Y), $nbt->getInt(self::TAG_DEATH_Z), $world);
}
} }
public function getLeaveMessage() : Translatable|string{ public function getLeaveMessage() : Translatable|string{
@ -1035,6 +1045,30 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
} }
} }
public function getDeathPosition() : ?Position{
if($this->deathPosition !== null && !$this->deathPosition->isValid()){
$this->deathPosition = null;
}
return $this->deathPosition;
}
/**
* @param Vector3|Position|null $pos
*/
public function setDeathPosition(?Vector3 $pos) : void{
if($pos !== null){
if($pos instanceof Position && $pos->world !== null){
$world = $pos->world;
}else{
$world = $this->getWorld();
}
$this->deathPosition = new Position($pos->x, $pos->y, $pos->z, $world);
}else{
$this->deathPosition = null;
}
$this->networkPropertiesDirty = true;
}
/** /**
* @return Position * @return Position
*/ */
@ -1474,6 +1508,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
return true; return true;
} }
public function canEat() : bool{
return $this->isCreative() || parent::canEat();
}
public function canBreathe() : bool{ public function canBreathe() : bool{
return $this->isCreative() || parent::canBreathe(); return $this->isCreative() || parent::canBreathe();
} }
@ -2333,6 +2371,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
protected function destroyCycles() : void{ protected function destroyCycles() : void{
$this->networkSession = null; $this->networkSession = null;
$this->spawnPosition = null; $this->spawnPosition = null;
$this->deathPosition = null;
$this->blockBreakHandler = null; $this->blockBreakHandler = null;
parent::destroyCycles(); parent::destroyCycles();
} }
@ -2374,6 +2413,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$nbt->setInt(self::TAG_SPAWN_Z, $spawn->getFloorZ()); $nbt->setInt(self::TAG_SPAWN_Z, $spawn->getFloorZ());
} }
if($this->deathPosition !== null && $this->deathPosition->isValid()){
$nbt->setString(self::TAG_DEATH_WORLD, $this->deathPosition->getWorld()->getFolderName());
$nbt->setInt(self::TAG_DEATH_X, $this->deathPosition->getFloorX());
$nbt->setInt(self::TAG_DEATH_Y, $this->deathPosition->getFloorY());
$nbt->setInt(self::TAG_DEATH_Z, $this->deathPosition->getFloorZ());
}
$nbt->setInt(self::TAG_GAME_MODE, GameModeIdMap::getInstance()->toId($this->gamemode)); $nbt->setInt(self::TAG_GAME_MODE, GameModeIdMap::getInstance()->toId($this->gamemode));
$nbt->setLong(self::TAG_FIRST_PLAYED, (int) $this->firstPlayed->format('Uv')); $nbt->setLong(self::TAG_FIRST_PLAYED, (int) $this->firstPlayed->format('Uv'));
$nbt->setLong(self::TAG_LAST_PLAYED, (int) floor(microtime(true) * 1000)); $nbt->setLong(self::TAG_LAST_PLAYED, (int) floor(microtime(true) * 1000));
@ -2393,6 +2439,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
//main inventory and drops the rest on the ground. //main inventory and drops the rest on the ground.
$this->removeCurrentWindow(); $this->removeCurrentWindow();
$this->setDeathPosition($this->getPosition());
$ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null); $ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null);
$ev->call(); $ev->call();
@ -2521,6 +2569,17 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$properties->setPlayerFlag(PlayerMetadataFlags::SLEEP, $this->sleeping !== null); $properties->setPlayerFlag(PlayerMetadataFlags::SLEEP, $this->sleeping !== null);
$properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping !== null ? BlockPosition::fromVector3($this->sleeping) : new BlockPosition(0, 0, 0)); $properties->setBlockPos(EntityMetadataProperties::PLAYER_BED_POSITION, $this->sleeping !== null ? BlockPosition::fromVector3($this->sleeping) : new BlockPosition(0, 0, 0));
if($this->deathPosition !== null && $this->deathPosition->world === $this->location->world){
$properties->setBlockPos(EntityMetadataProperties::PLAYER_DEATH_POSITION, BlockPosition::fromVector3($this->deathPosition));
//TODO: this should be updated when dimensions are implemented
$properties->setInt(EntityMetadataProperties::PLAYER_DEATH_DIMENSION, DimensionIds::OVERWORLD);
$properties->setByte(EntityMetadataProperties::PLAYER_HAS_DIED, 1);
}else{
$properties->setBlockPos(EntityMetadataProperties::PLAYER_DEATH_POSITION, new BlockPosition(0, 0, 0));
$properties->setInt(EntityMetadataProperties::PLAYER_DEATH_DIMENSION, DimensionIds::OVERWORLD);
$properties->setByte(EntityMetadataProperties::PLAYER_HAS_DIED, 0);
}
} }
public function sendData(?array $targets, ?array $data = null) : void{ public function sendData(?array $targets, ?array $data = null) : void{

View File

@ -27,6 +27,7 @@ use pmmp\thread\Runnable;
use pmmp\thread\ThreadSafe; use pmmp\thread\ThreadSafe;
use pmmp\thread\ThreadSafeArray; use pmmp\thread\ThreadSafeArray;
use pocketmine\thread\NonThreadSafeValue; use pocketmine\thread\NonThreadSafeValue;
use pocketmine\timings\Timings;
use function array_key_exists; use function array_key_exists;
use function igbinary_serialize; use function igbinary_serialize;
use function igbinary_unserialize; use function igbinary_unserialize;
@ -67,7 +68,10 @@ abstract class AsyncTask extends Runnable{
*/ */
private static array $threadLocalStorage = []; private static array $threadLocalStorage = [];
/** @phpstan-var ThreadSafeArray<int, string>|null */ /**
* @phpstan-var ThreadSafeArray<int, string>|null
* @deprecated
*/
private ?ThreadSafeArray $progressUpdates = null; private ?ThreadSafeArray $progressUpdates = null;
private ThreadSafe|string|int|bool|null|float $result = null; private ThreadSafe|string|int|bool|null|float $result = null;
@ -78,7 +82,14 @@ abstract class AsyncTask extends Runnable{
public function run() : void{ public function run() : void{
$this->result = null; $this->result = null;
$timings = Timings::getAsyncTaskRunTimings($this);
$timings->startTiming();
try{
$this->onRun(); $this->onRun();
}finally{
$timings->stopTiming();
}
$this->finished = true; $this->finished = true;
AsyncWorker::getNotifier()->wakeupSleeper(); AsyncWorker::getNotifier()->wakeupSleeper();
@ -132,6 +143,8 @@ abstract class AsyncTask extends Runnable{
} }
/** /**
* @deprecated
*
* Call this method from {@link AsyncTask::onRun} (AsyncTask execution thread) to schedule a call to * Call this method from {@link AsyncTask::onRun} (AsyncTask execution thread) to schedule a call to
* {@link AsyncTask::onProgressUpdate} from the main thread with the given progress parameter. * {@link AsyncTask::onProgressUpdate} from the main thread with the given progress parameter.
* *
@ -146,6 +159,7 @@ abstract class AsyncTask extends Runnable{
} }
/** /**
* @deprecated
* @internal Only call from AsyncPool.php on the main thread * @internal Only call from AsyncPool.php on the main thread
*/ */
public function checkProgressUpdates() : void{ public function checkProgressUpdates() : void{
@ -158,6 +172,8 @@ abstract class AsyncTask extends Runnable{
} }
/** /**
* @deprecated
*
* Called from the main thread after {@link AsyncTask::publishProgress} is called. * Called from the main thread after {@link AsyncTask::publishProgress} is called.
* All {@link AsyncTask::publishProgress} calls should result in {@link AsyncTask::onProgressUpdate} calls before * All {@link AsyncTask::publishProgress} calls should result in {@link AsyncTask::onProgressUpdate} calls before
* {@link AsyncTask::onCompletion} is called. * {@link AsyncTask::onCompletion} is called.

View File

@ -0,0 +1,60 @@
<?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\scheduler;
use pocketmine\promise\PromiseResolver;
use pocketmine\timings\TimingsHandler;
/**
* @phpstan-type Resolver PromiseResolver<list<string>>
*/
final class TimingsCollectionTask extends AsyncTask{
private const TLS_KEY_RESOLVER = "resolver";
/**
* @phpstan-param PromiseResolver<list<string>> $promiseResolver
*/
public function __construct(PromiseResolver $promiseResolver){
$this->storeLocal(self::TLS_KEY_RESOLVER, $promiseResolver);
}
public function onRun() : void{
$this->setResult(TimingsHandler::printCurrentThreadRecords());
}
public function onCompletion() : void{
/**
* @var string[] $result
* @phpstan-var list<string> $result
*/
$result = $this->getResult();
/**
* @var PromiseResolver $promiseResolver
* @phpstan-var PromiseResolver<list<string>> $promiseResolver
*/
$promiseResolver = $this->fetchLocal(self::TLS_KEY_RESOLVER);
$promiseResolver->resolve($result);
}
}

View File

@ -0,0 +1,60 @@
<?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\scheduler;
use pocketmine\timings\TimingsHandler;
final class TimingsControlTask extends AsyncTask{
private const ENABLE = 1;
private const DISABLE = 2;
private const RELOAD = 3;
private function __construct(
private int $operation
){}
public static function setEnabled(bool $enable) : self{
return new self($enable ? self::ENABLE : self::DISABLE);
}
public static function reload() : self{
return new self(self::RELOAD);
}
public function onRun() : void{
if($this->operation === self::ENABLE){
TimingsHandler::setEnabled(true);
\GlobalLogger::get()->debug("Enabled timings");
}elseif($this->operation === self::DISABLE){
TimingsHandler::setEnabled(false);
\GlobalLogger::get()->debug("Disabled timings");
}elseif($this->operation === self::RELOAD){
TimingsHandler::reload();
\GlobalLogger::get()->debug("Reset timings");
}else{
throw new \InvalidArgumentException("Invalid operation $this->operation");
}
}
}

View File

@ -37,6 +37,7 @@ use function str_starts_with;
abstract class Timings{ abstract class Timings{
public const GROUP_MINECRAFT = "Minecraft"; public const GROUP_MINECRAFT = "Minecraft";
/** @deprecated No longer used */
public const GROUP_BREAKDOWN = "Minecraft - Breakdown"; public const GROUP_BREAKDOWN = "Minecraft - Breakdown";
private static bool $initialized = false; private static bool $initialized = false;
@ -124,11 +125,16 @@ abstract class Timings{
/** @var TimingsHandler[] */ /** @var TimingsHandler[] */
private static array $asyncTaskProgressUpdate = []; private static array $asyncTaskProgressUpdate = [];
/** @var TimingsHandler[] */ /** @var TimingsHandler[] */
private static array $asyncTaskCompletion = []; private static array $asyncTaskCompletion = [];
/** @var TimingsHandler[] */ /** @var TimingsHandler[] */
private static array $asyncTaskError = []; private static array $asyncTaskError = [];
private static TimingsHandler $asyncTaskWorkers;
/** @var TimingsHandler[] */
private static array $asyncTaskRun = [];
public static function init() : void{ public static function init() : void{
if(self::$initialized){ if(self::$initialized){
return; return;
@ -188,6 +194,8 @@ abstract class Timings{
self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync); self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync);
self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync); self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync);
self::$asyncTaskWorkers = new TimingsHandler("Async Task Workers");
self::$playerCommand = new TimingsHandler("Player Command"); self::$playerCommand = new TimingsHandler("Player Command");
self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache"); self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache");
@ -344,6 +352,9 @@ abstract class Timings{
return self::$asyncTaskCompletion[$taskClass]; return self::$asyncTaskCompletion[$taskClass];
} }
/**
* @deprecated No longer used
*/
public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{ public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
$taskClass = $task::class; $taskClass = $task::class;
if(!isset(self::$asyncTaskError[$taskClass])){ if(!isset(self::$asyncTaskError[$taskClass])){
@ -357,4 +368,18 @@ abstract class Timings{
return self::$asyncTaskError[$taskClass]; return self::$asyncTaskError[$taskClass];
} }
public static function getAsyncTaskRunTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
$taskClass = $task::class;
if(!isset(self::$asyncTaskRun[$taskClass])){
self::init();
self::$asyncTaskRun[$taskClass] = new TimingsHandler(
"AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Run",
self::$asyncTaskWorkers,
$group
);
}
return self::$asyncTaskRun[$taskClass];
}
} }

View File

@ -23,20 +23,66 @@ declare(strict_types=1);
namespace pocketmine\timings; namespace pocketmine\timings;
use pmmp\thread\Thread as NativeThread;
use pocketmine\promise\Promise;
use pocketmine\promise\PromiseResolver;
use pocketmine\Server; use pocketmine\Server;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use function array_merge;
use function array_push;
use function hrtime; use function hrtime;
use function implode; use function implode;
use function spl_object_id; use function spl_object_id;
/**
* @phpstan-type CollectPromise Promise<list<string>>
*/
class TimingsHandler{ class TimingsHandler{
private const FORMAT_VERSION = 2; //peak timings fix private const FORMAT_VERSION = 3; //thread timings collection
private static bool $enabled = false; private static bool $enabled = false;
private static int $timingStart = 0; private static int $timingStart = 0;
/** @return string[] */ /** @phpstan-var ObjectSet<\Closure(bool $enable) : void> */
public static function printTimings() : array{ private static ?ObjectSet $toggleCallbacks = null;
/** @phpstan-var ObjectSet<\Closure() : void> */
private static ?ObjectSet $reloadCallbacks = null;
/** @phpstan-var ObjectSet<\Closure() : list<CollectPromise>> */
private static ?ObjectSet $collectCallbacks = null;
/**
* @phpstan-template T of object
* @phpstan-param ?ObjectSet<T> $where
* @phpstan-param-out ObjectSet<T> $where
* @phpstan-return ObjectSet<T>
*/
private static function lazyGetSet(?ObjectSet &$where) : ObjectSet{
//workaround for phpstan bug - allows us to ignore 1 error instead of 6 without suppressing other errors
return $where ??= new ObjectSet();
}
/**
* @phpstan-return ObjectSet<\Closure(bool $enable) : void>
*/
public static function getToggleCallbacks() : ObjectSet{ return self::lazyGetSet(self::$toggleCallbacks); }
/**
* @phpstan-return ObjectSet<\Closure() : void>
*/
public static function getReloadCallbacks() : ObjectSet{ return self::lazyGetSet(self::$reloadCallbacks); }
/**
* @phpstan-return ObjectSet<\Closure() : list<CollectPromise>>
*/
public static function getCollectCallbacks() : ObjectSet{ return self::lazyGetSet(self::$collectCallbacks); }
/**
* @return string[]
* @phpstan-return list<string>
*/
public static function printCurrentThreadRecords() : array{
$threadId = NativeThread::getCurrentThread()?->getThreadId();
$groups = []; $groups = [];
foreach(TimingsRecord::getAll() as $timings){ foreach(TimingsRecord::getAll() as $timings){
@ -49,7 +95,7 @@ class TimingsHandler{
$avg = $time / $count; $avg = $time / $count;
$group = $timings->getGroup(); $group = $timings->getGroup() . ($threadId !== null ? " ThreadId: $threadId" : "");
$groups[$group][] = implode(" ", [ $groups[$group][] = implode(" ", [
$timings->getName(), $timings->getName(),
"Time: $time", "Time: $time",
@ -72,6 +118,15 @@ class TimingsHandler{
} }
} }
return $result;
}
/**
* @return string[]
*/
private static function printFooter() : array{
$result = [];
$result[] = "# Version " . Server::getInstance()->getVersion(); $result[] = "# Version " . Server::getInstance()->getVersion();
$result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion(); $result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion();
@ -79,29 +134,95 @@ class TimingsHandler{
$sampleTime = hrtime(true) - self::$timingStart; $sampleTime = hrtime(true) - self::$timingStart;
$result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)"; $result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)";
return $result; return $result;
} }
/**
* @deprecated This only collects timings from the main thread. Collecting timings from all threads is an async
* operation, so it can't be done synchronously.
*
* @return string[]
*/
public static function printTimings() : array{
$records = self::printCurrentThreadRecords();
$footer = self::printFooter();
return [...$records, ...$footer];
}
/**
* Collects timings asynchronously, allowing timings from multiple threads to be aggregated into a single report.
*
* NOTE: You need to add a callback to collectCallbacks if you want to include timings from other threads. They
* won't be automatically collected if you don't, since the main thread has no way to access them.
*
* This is an asynchronous operation, and the result is returned as a promise.
* The caller must add a callback to the returned promise to get the complete timings report.
*
* @phpstan-return Promise<list<string>>
*/
public static function requestPrintTimings() : Promise{
$thisThreadRecords = self::printCurrentThreadRecords();
$otherThreadRecordPromises = [];
if(self::$collectCallbacks !== null){
foreach(self::$collectCallbacks as $callback){
$callbackPromises = $callback();
array_push($otherThreadRecordPromises, ...$callbackPromises);
}
}
$resolver = new PromiseResolver();
Promise::all($otherThreadRecordPromises)->onCompletion(
function(array $promisedRecords) use ($resolver, $thisThreadRecords) : void{
$resolver->resolve([...$thisThreadRecords, ...array_merge(...$promisedRecords), ...self::printFooter()]);
},
function() : void{
throw new \AssertionError("This promise is not expected to be rejected");
}
);
return $resolver->getPromise();
}
public static function isEnabled() : bool{ public static function isEnabled() : bool{
return self::$enabled; return self::$enabled;
} }
public static function setEnabled(bool $enable = true) : void{ public static function setEnabled(bool $enable = true) : void{
if($enable === self::$enabled){
return;
}
self::$enabled = $enable; self::$enabled = $enable;
self::reload(); self::internalReload();
if(self::$toggleCallbacks !== null){
foreach(self::$toggleCallbacks as $callback){
$callback($enable);
}
}
} }
public static function getStartTime() : float{ public static function getStartTime() : float{
return self::$timingStart; return self::$timingStart;
} }
public static function reload() : void{ private static function internalReload() : void{
TimingsRecord::reset(); TimingsRecord::reset();
if(self::$enabled){ if(self::$enabled){
self::$timingStart = hrtime(true); self::$timingStart = hrtime(true);
} }
} }
public static function reload() : void{
self::internalReload();
if(self::$reloadCallbacks !== null){
foreach(self::$reloadCallbacks as $callback){
$callback();
}
}
}
public static function tick(bool $measure = true) : void{ public static function tick(bool $measure = true) : void{
if(self::$enabled){ if(self::$enabled){
TimingsRecord::tick($measure); TimingsRecord::tick($measure);

View File

@ -67,6 +67,8 @@ use function is_nan;
use function is_object; use function is_object;
use function is_string; use function is_string;
use function mb_check_encoding; use function mb_check_encoding;
use function mt_getrandmax;
use function mt_rand;
use function ob_end_clean; use function ob_end_clean;
use function ob_get_contents; use function ob_get_contents;
use function ob_start; use function ob_start;
@ -688,4 +690,12 @@ final class Utils{
//jit not available //jit not available
return null; return null;
} }
/**
* Returns a random float between 0.0 and 1.0
* Drop-in replacement for lcg_value()
*/
public static function getRandomFloat() : float{
return mt_rand() / mt_getrandmax();
}
} }

View File

@ -83,6 +83,7 @@ use pocketmine\ServerConfigGroup;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Limits; use pocketmine\utils\Limits;
use pocketmine\utils\ReversePriorityQueue; use pocketmine\utils\ReversePriorityQueue;
use pocketmine\utils\Utils;
use pocketmine\world\biome\Biome; use pocketmine\world\biome\Biome;
use pocketmine\world\biome\BiomeRegistry; use pocketmine\world\biome\BiomeRegistry;
use pocketmine\world\format\Chunk; use pocketmine\world\format\Chunk;
@ -120,7 +121,6 @@ use function get_class;
use function gettype; use function gettype;
use function is_a; use function is_a;
use function is_object; use function is_object;
use function lcg_value;
use function max; use function max;
use function microtime; use function microtime;
use function min; use function min;
@ -1998,10 +1998,10 @@ class World implements ChunkManager{
return null; return null;
} }
$itemEntity = new ItemEntity(Location::fromObject($source, $this, lcg_value() * 360, 0), $item); $itemEntity = new ItemEntity(Location::fromObject($source, $this, Utils::getRandomFloat() * 360, 0), $item);
$itemEntity->setPickupDelay($delay); $itemEntity->setPickupDelay($delay);
$itemEntity->setMotion($motion ?? new Vector3(lcg_value() * 0.2 - 0.1, 0.2, lcg_value() * 0.2 - 0.1)); $itemEntity->setMotion($motion ?? new Vector3(Utils::getRandomFloat() * 0.2 - 0.1, 0.2, Utils::getRandomFloat() * 0.2 - 0.1));
$itemEntity->spawnToAll(); $itemEntity->spawnToAll();
return $itemEntity; return $itemEntity;
@ -2018,9 +2018,9 @@ class World implements ChunkManager{
$orbs = []; $orbs = [];
foreach(ExperienceOrb::splitIntoOrbSizes($amount) as $split){ foreach(ExperienceOrb::splitIntoOrbSizes($amount) as $split){
$orb = new ExperienceOrb(Location::fromObject($pos, $this, lcg_value() * 360, 0), $split); $orb = new ExperienceOrb(Location::fromObject($pos, $this, Utils::getRandomFloat() * 360, 0), $split);
$orb->setMotion(new Vector3((lcg_value() * 0.2 - 0.1) * 2, lcg_value() * 0.4, (lcg_value() * 0.2 - 0.1) * 2)); $orb->setMotion(new Vector3((Utils::getRandomFloat() * 0.2 - 0.1) * 2, Utils::getRandomFloat() * 0.4, (Utils::getRandomFloat() * 0.2 - 0.1) * 2));
$orb->spawnToAll(); $orb->spawnToAll();
$orbs[] = $orb; $orbs[] = $orb;

View File

@ -90,6 +90,11 @@ parameters:
count: 1 count: 1
path: ../../../src/plugin/PluginManager.php path: ../../../src/plugin/PluginManager.php
-
message: "#^Method pocketmine\\\\timings\\\\TimingsHandler\\:\\:lazyGetSet\\(\\) should return pocketmine\\\\utils\\\\ObjectSet\\<T of object\\> but returns pocketmine\\\\utils\\\\ObjectSet\\<object\\>\\.$#"
count: 1
path: ../../../src/timings/TimingsHandler.php
- -
message: "#^Casting to int something that's already int\\.$#" message: "#^Casting to int something that's already int\\.$#"
count: 1 count: 1

View File

@ -27,6 +27,7 @@ use PHPUnit\Framework\TestCase;
use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem; use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
use function get_debug_type;
use function implode; use function implode;
use function is_array; use function is_array;
use function is_int; use function is_int;
@ -95,11 +96,12 @@ class BlockTest extends TestCase{
} }
/** /**
* @return int[] * @return int[][]|string[][]
* @phpstan-return array<string, int> * @phpstan-return array{array<string, int>, array<string, string>}
*/ */
public static function computeConsistencyCheckTable(RuntimeBlockStateRegistry $blockStateRegistry) : array{ public static function computeConsistencyCheckTable(RuntimeBlockStateRegistry $blockStateRegistry) : array{
$newTable = []; $newTable = [];
$newTileMap = [];
$idNameLookup = []; $idNameLookup = [];
//if we ever split up block registration into multiple registries (e.g. separating chemistry blocks), //if we ever split up block registration into multiple registries (e.g. separating chemistry blocks),
@ -118,36 +120,70 @@ class BlockTest extends TestCase{
} }
$idName = $idNameLookup[$block->getTypeId()]; $idName = $idNameLookup[$block->getTypeId()];
$newTable[$idName] = ($newTable[$idName] ?? 0) + 1; $newTable[$idName] = ($newTable[$idName] ?? 0) + 1;
}
return $newTable; $tileClass = $block->getIdInfo()->getTileClass();
if($tileClass !== null){
if(isset($newTileMap[$idName]) && $newTileMap[$idName] !== $tileClass){
throw new AssumptionFailedError("Tile entity $tileClass for $idName is inconsistent");
}
$newTileMap[$idName] = $tileClass;
}
}
return [$newTable, $newTileMap];
} }
/** /**
* @phpstan-param array<string, int> $actual * @phpstan-param array<string, int> $actualStateCounts
* @phpstan-param array<string, string> $actualTiles
* *
* @return string[] * @return string[]
*/ */
public static function computeConsistencyCheckDiff(string $expectedFile, array $actual) : array{ public static function computeConsistencyCheckDiff(string $expectedFile, array $actualStateCounts, array $actualTiles) : array{
$expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 2, JSON_THROW_ON_ERROR); $expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 3, JSON_THROW_ON_ERROR);
if(!is_array($expected)){ if(!is_array($expected)){
throw new AssumptionFailedError("Old table should be array<string, int>"); throw new AssumptionFailedError("Old table should be array{stateCounts: array<string, int>, tiles: array<string, string>}");
}
$expectedStates = $expected["stateCounts"] ?? [];
$expectedTiles = $expected["tiles"] ?? [];
if(!is_array($expectedStates)){
throw new AssumptionFailedError("stateCounts should be an array, but have " . get_debug_type($expectedStates));
}
if(!is_array($expectedTiles)){
throw new AssumptionFailedError("tiles should be an array, but have " . get_debug_type($expectedTiles));
} }
$errors = []; $errors = [];
foreach(Utils::promoteKeys($expected) as $typeName => $numStates){ foreach(Utils::promoteKeys($expectedStates) as $typeName => $numStates){
if(!is_string($typeName) || !is_int($numStates)){ if(!is_string($typeName) || !is_int($numStates)){
throw new AssumptionFailedError("Old table should be array<string, int>"); throw new AssumptionFailedError("Old table should be array<string, int>");
} }
if(!isset($actual[$typeName])){ if(!isset($actualStateCounts[$typeName])){
$errors[] = "Removed block type $typeName ($numStates permutations)"; $errors[] = "Removed block type $typeName ($numStates permutations)";
}elseif($actual[$typeName] !== $numStates){ }elseif($actualStateCounts[$typeName] !== $numStates){
$errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actual[$typeName]; $errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actualStateCounts[$typeName];
} }
} }
foreach(Utils::stringifyKeys($actual) as $typeName => $numStates){ foreach(Utils::stringifyKeys($actualStateCounts) as $typeName => $numStates){
if(!isset($expected[$typeName])){ if(!isset($expectedStates[$typeName])){
$errors[] = "Added block type $typeName (" . $actual[$typeName] . " permutations)"; $errors[] = "Added block type $typeName (" . $actualStateCounts[$typeName] . " permutations)";
}
}
foreach(Utils::promoteKeys($expectedTiles) as $typeName => $tile){
if(!is_string($typeName) || !is_string($tile)){
throw new AssumptionFailedError("Tile table should be array<string, string>");
}
if(isset($actualStateCounts[$typeName])){
if(!isset($actualTiles[$typeName])){
$errors[] = "$typeName no longer has a tile";
}elseif($actualTiles[$typeName] !== $tile){
$errors[] = "$typeName has changed tile ($tile -> " . $actualTiles[$typeName] . ")";
}
}
}
foreach(Utils::promoteKeys($actualTiles) as $typeName => $tile){
if(isset($expectedStates[$typeName]) && !isset($expectedTiles[$typeName])){
$errors[] = "$typeName has a tile when it previously didn't ($tile)";
} }
} }
@ -155,8 +191,8 @@ class BlockTest extends TestCase{
} }
public function testConsistency() : void{ public function testConsistency() : void{
$newTable = self::computeConsistencyCheckTable($this->blockFactory); [$newTable, $newTileMap] = self::computeConsistencyCheckTable($this->blockFactory);
$errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable); $errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable, $newTileMap);
self::assertEmpty($errors, "Block factory consistency check failed:\n" . implode("\n", $errors)); self::assertEmpty($errors, "Block factory consistency check failed:\n" . implode("\n", $errors));
} }

View File

@ -1,4 +1,5 @@
{ {
"stateCounts": {
"ACACIA_BUTTON": 12, "ACACIA_BUTTON": 12,
"ACACIA_DOOR": 32, "ACACIA_DOOR": 32,
"ACACIA_FENCE": 1, "ACACIA_FENCE": 1,
@ -709,4 +710,61 @@
"WHITE_TULIP": 1, "WHITE_TULIP": 1,
"WITHER_ROSE": 1, "WITHER_ROSE": 1,
"WOOL": 16 "WOOL": 16
},
"tiles": {
"ACACIA_SIGN": "pocketmine\\block\\tile\\Sign",
"ACACIA_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"BANNER": "pocketmine\\block\\tile\\Banner",
"BARREL": "pocketmine\\block\\tile\\Barrel",
"BEACON": "pocketmine\\block\\tile\\Beacon",
"BED": "pocketmine\\block\\tile\\Bed",
"BELL": "pocketmine\\block\\tile\\Bell",
"BIRCH_SIGN": "pocketmine\\block\\tile\\Sign",
"BIRCH_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"BLAST_FURNACE": "pocketmine\\block\\tile\\BlastFurnace",
"BREWING_STAND": "pocketmine\\block\\tile\\BrewingStand",
"CAMPFIRE": "pocketmine\\block\\tile\\Campfire",
"CAULDRON": "pocketmine\\block\\tile\\Cauldron",
"CHERRY_SIGN": "pocketmine\\block\\tile\\Sign",
"CHERRY_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"CHEST": "pocketmine\\block\\tile\\Chest",
"CHISELED_BOOKSHELF": "pocketmine\\block\\tile\\ChiseledBookshelf",
"CRIMSON_SIGN": "pocketmine\\block\\tile\\Sign",
"CRIMSON_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"DARK_OAK_SIGN": "pocketmine\\block\\tile\\Sign",
"DARK_OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"DAYLIGHT_SENSOR": "pocketmine\\block\\tile\\DaylightSensor",
"DYED_SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox",
"ENCHANTING_TABLE": "pocketmine\\block\\tile\\EnchantTable",
"ENDER_CHEST": "pocketmine\\block\\tile\\EnderChest",
"FLOWER_POT": "pocketmine\\block\\tile\\FlowerPot",
"FURNACE": "pocketmine\\block\\tile\\NormalFurnace",
"GLOWING_ITEM_FRAME": "pocketmine\\block\\tile\\GlowingItemFrame",
"HOPPER": "pocketmine\\block\\tile\\Hopper",
"ITEM_FRAME": "pocketmine\\block\\tile\\ItemFrame",
"JUKEBOX": "pocketmine\\block\\tile\\Jukebox",
"JUNGLE_SIGN": "pocketmine\\block\\tile\\Sign",
"JUNGLE_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"LAVA_CAULDRON": "pocketmine\\block\\tile\\Cauldron",
"LECTERN": "pocketmine\\block\\tile\\Lectern",
"MANGROVE_SIGN": "pocketmine\\block\\tile\\Sign",
"MANGROVE_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"MOB_HEAD": "pocketmine\\block\\tile\\MobHead",
"MONSTER_SPAWNER": "pocketmine\\block\\tile\\MonsterSpawner",
"NOTE_BLOCK": "pocketmine\\block\\tile\\Note",
"OAK_SIGN": "pocketmine\\block\\tile\\Sign",
"OAK_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"POTION_CAULDRON": "pocketmine\\block\\tile\\Cauldron",
"REDSTONE_COMPARATOR": "pocketmine\\block\\tile\\Comparator",
"SHULKER_BOX": "pocketmine\\block\\tile\\ShulkerBox",
"SMOKER": "pocketmine\\block\\tile\\Smoker",
"SOUL_CAMPFIRE": "pocketmine\\block\\tile\\Campfire",
"SPRUCE_SIGN": "pocketmine\\block\\tile\\Sign",
"SPRUCE_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"TRAPPED_CHEST": "pocketmine\\block\\tile\\Chest",
"WALL_BANNER": "pocketmine\\block\\tile\\Banner",
"WARPED_SIGN": "pocketmine\\block\\tile\\Sign",
"WARPED_WALL_SIGN": "pocketmine\\block\\tile\\Sign",
"WATER_CAULDRON": "pocketmine\\block\\tile\\Cauldron"
}
} }

View File

@ -28,11 +28,11 @@ require dirname(__DIR__, 3) . '/vendor/autoload.php';
/* This script needs to be re-run after any intentional blockfactory change (adding or removing a block state). */ /* This script needs to be re-run after any intentional blockfactory change (adding or removing a block state). */
$newTable = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance()); [$newTable, $newTiles] = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance());
$oldTablePath = __DIR__ . '/block_factory_consistency_check.json'; $oldTablePath = __DIR__ . '/block_factory_consistency_check.json';
if(file_exists($oldTablePath)){ if(file_exists($oldTablePath)){
$errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable); $errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable, $newTiles);
if(count($errors) > 0){ if(count($errors) > 0){
echo count($errors) . " changes detected:\n"; echo count($errors) . " changes detected:\n";
@ -47,5 +47,6 @@ if(file_exists($oldTablePath)){
} }
ksort($newTable, SORT_STRING); ksort($newTable, SORT_STRING);
ksort($newTiles, SORT_STRING);
file_put_contents($oldTablePath, json_encode($newTable, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT)); file_put_contents($oldTablePath, json_encode(["stateCounts" => $newTable, "tiles" => $newTiles], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));