mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-09 07:20:15 +00:00
Merge branch 'major-next' into inventory-rework
This commit is contained in:
commit
6578d65cd8
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -3,9 +3,6 @@ contact_links:
|
||||
- name: Help & support on Discord
|
||||
url: https://discord.gg/bmSAZBG
|
||||
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
|
||||
url: https://pmmp.rtfd.io
|
||||
about: PocketMine-MP documentation
|
||||
|
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@ -37,4 +37,7 @@ updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
interval: monthly
|
||||
groups:
|
||||
github-actions:
|
||||
patterns: ["*"]
|
||||
|
8
.github/workflows/build-docker-image.yml
vendored
8
.github/workflows/build-docker-image.yml
vendored
@ -53,7 +53,7 @@ jobs:
|
||||
run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build image for tag
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
uses: docker/build-push-action@v6.10.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
- name: Build image for major tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
uses: docker/build-push-action@v6.10.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
- name: Build image for minor tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
uses: docker/build-push-action@v6.10.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
@ -92,7 +92,7 @@ jobs:
|
||||
|
||||
- name: Build image for latest tag
|
||||
if: steps.channel.outputs.CHANNEL == 'stable'
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
uses: docker/build-push-action@v6.10.0
|
||||
with:
|
||||
push: true
|
||||
context: ./pocketmine-mp
|
||||
|
3
.github/workflows/draft-release.yml
vendored
3
.github/workflows/draft-release.yml
vendored
@ -64,6 +64,7 @@ jobs:
|
||||
id: get-pm-version
|
||||
run: |
|
||||
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 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
|
||||
@ -72,7 +73,7 @@ jobs:
|
||||
- name: Generate PHP binary download URL
|
||||
id: php-binary-url
|
||||
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
|
||||
run: |
|
||||
|
8
.github/workflows/main-php-matrix.yml
vendored
8
.github/workflows/main-php-matrix.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.1.0
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
with:
|
||||
php-version: ${{ inputs.php }}
|
||||
install-path: "./bin"
|
||||
@ -62,7 +62,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.1.0
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
with:
|
||||
php-version: ${{ inputs.php }}
|
||||
install-path: "./bin"
|
||||
@ -96,7 +96,7 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.1.0
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
with:
|
||||
php-version: ${{ inputs.php }}
|
||||
install-path: "./bin"
|
||||
@ -128,7 +128,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@3.1.0
|
||||
uses: pmmp/setup-php-action@3.2.0
|
||||
with:
|
||||
php-version: ${{ inputs.php }}
|
||||
install-path: "./bin"
|
||||
|
29
.github/workflows/pr-stale.yml
vendored
Normal file
29
.github/workflows/pr-stale.yml
vendored
Normal 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
|
||||
|
@ -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.
|
||||
- **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.
|
||||
- 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 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.
|
||||
|
@ -36,6 +36,7 @@ require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
*/
|
||||
$options = [
|
||||
"base_version" => VersionInfo::BASE_VERSION,
|
||||
"major_version" => fn() => explode(".", VersionInfo::BASE_VERSION)[0],
|
||||
"mcpe_version" => ProtocolInfo::MINECRAFT_VERSION_NETWORK,
|
||||
"is_dev" => VersionInfo::IS_DEVELOPMENT_BUILD,
|
||||
"changelog_file_name" => function() : string{
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit a51259d7a6ea649d64f409fc0276baa59cf4f19a
|
||||
Subproject commit 5016e0a3d54c714c12b331ea0474a6f500ffc0a3
|
@ -110,3 +110,19 @@ Released 12th November 2024.
|
||||
- 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 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
16
changelogs/5.22.md
Normal 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
116
changelogs/5.23.md
Normal 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.
|
@ -32,16 +32,16 @@
|
||||
"ext-zlib": ">=1.2.11",
|
||||
"composer-runtime-api": "^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-data": "~2.14.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.13.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-data": "~2.15.0+bedrock-1.21.50",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
|
||||
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.50",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
"pocketmine/errorhandler": "^0.7.0",
|
||||
"pocketmine/locale-data": "~2.21.0",
|
||||
"pocketmine/locale-data": "~2.22.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/math": "~1.0.0",
|
||||
"pocketmine/nbt": "~1.0.0",
|
||||
|
155
composer.lock
generated
155
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "476374fb3d22e26a97c1dea8c6736faf",
|
||||
"content-hash": "732102eca72dc1d29e7b67dfbce07653",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -125,6 +125,57 @@
|
||||
],
|
||||
"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",
|
||||
"version": "5.0.0",
|
||||
@ -153,16 +204,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-data",
|
||||
"version": "2.14.1+bedrock-1.21.40",
|
||||
"version": "2.15.0+bedrock-1.21.50",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockData.git",
|
||||
"reference": "4a41864ed09613ecec6791e2ae076a8ec7089cc4"
|
||||
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/4a41864ed09613ecec6791e2ae076a8ec7089cc4",
|
||||
"reference": "4a41864ed09613ecec6791e2ae076a8ec7089cc4",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
|
||||
"reference": "6e819f36d781866ce63d2406be2ce7f2d1afd9ad",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -173,22 +224,22 @@
|
||||
"description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockData/issues",
|
||||
"source": "https://github.com/pmmp/BedrockData/tree/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",
|
||||
"version": "1.13.1",
|
||||
"version": "1.14.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockItemUpgradeSchema.git",
|
||||
"reference": "1cf81305f2ffcf7dde9577c4f16a55c765192b03"
|
||||
"reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/1cf81305f2ffcf7dde9577c4f16a55c765192b03",
|
||||
"reference": "1cf81305f2ffcf7dde9577c4f16a55c765192b03",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockItemUpgradeSchema/zipball/9fc7c9bbb558a017395c1cb7dd819c033ee971bb",
|
||||
"reference": "9fc7c9bbb558a017395c1cb7dd819c033ee971bb",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -199,22 +250,22 @@
|
||||
"description": "JSON schemas for upgrading items found in older Minecraft: Bedrock world saves",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockItemUpgradeSchema/issues",
|
||||
"source": "https://github.com/pmmp/BedrockItemUpgradeSchema/tree/1.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",
|
||||
"version": "35.0.0+bedrock-1.21.40",
|
||||
"version": "35.0.0+bedrock-1.21.50",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockProtocol.git",
|
||||
"reference": "6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459"
|
||||
"reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459",
|
||||
"reference": "6aa7cbeb4a7ec6fa58f9024aeaddad7c5c65a459",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
|
||||
"reference": "bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -245,9 +296,9 @@
|
||||
"description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockProtocol/issues",
|
||||
"source": "https://github.com/pmmp/BedrockProtocol/tree/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",
|
||||
@ -420,16 +471,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
"version": "2.21.1",
|
||||
"version": "2.22.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "fdba0f764d6281f64e5968dca94fdab96bf4e167"
|
||||
"reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/fdba0f764d6281f64e5968dca94fdab96bf4e167",
|
||||
"reference": "fdba0f764d6281f64e5968dca94fdab96bf4e167",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
|
||||
"reference": "aed64e9ca92ffbb20788b3b3bb75b60e4f0eae2d",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -437,9 +488,9 @@
|
||||
"description": "Language resources used by PocketMine-MP",
|
||||
"support": {
|
||||
"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",
|
||||
@ -565,60 +616,6 @@
|
||||
},
|
||||
"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",
|
||||
"version": "1.1.1",
|
||||
|
@ -90,6 +90,8 @@ use pocketmine\promise\Promise;
|
||||
use pocketmine\promise\PromiseResolver;
|
||||
use pocketmine\resourcepacks\ResourcePackManager;
|
||||
use pocketmine\scheduler\AsyncPool;
|
||||
use pocketmine\scheduler\TimingsCollectionTask;
|
||||
use pocketmine\scheduler\TimingsControlTask;
|
||||
use pocketmine\snooze\SleeperHandler;
|
||||
use pocketmine\stats\SendUsageTask;
|
||||
use pocketmine\thread\log\AttachableThreadSafeLogger;
|
||||
@ -343,6 +345,10 @@ class Server{
|
||||
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
|
||||
* are not logged into Xbox Live will be disconnected.
|
||||
@ -895,7 +901,36 @@ class Server{
|
||||
$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->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;
|
||||
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())));
|
||||
|
||||
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();
|
||||
|
||||
$this->commandMap = new SimpleCommandMap($this);
|
||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
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 BUILD_CHANNEL = "stable";
|
||||
|
||||
|
@ -35,10 +35,10 @@ use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\sound\AnvilFallSound;
|
||||
use pocketmine\world\sound\Sound;
|
||||
use function lcg_value;
|
||||
use function round;
|
||||
|
||||
class Anvil extends Transparent implements Fallable{
|
||||
@ -97,7 +97,7 @@ class Anvil extends Transparent implements Fallable{
|
||||
}
|
||||
|
||||
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){
|
||||
$this->damage = $this->damage + 1;
|
||||
}else{
|
||||
|
@ -69,6 +69,10 @@ class Campfire extends Transparent{
|
||||
|
||||
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;
|
||||
|
||||
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)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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{
|
||||
return $this->inventory;
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ use pocketmine\event\entity\EntityTrampleFarmlandEvent;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\utils\Utils;
|
||||
use function intdiv;
|
||||
use function lcg_value;
|
||||
|
||||
class Farmland extends Transparent{
|
||||
public const MAX_WETNESS = 7;
|
||||
@ -148,7 +148,7 @@ class Farmland extends Transparent{
|
||||
}
|
||||
|
||||
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->call();
|
||||
if(!$ev->isCancelled()){
|
||||
|
@ -31,13 +31,13 @@ use pocketmine\item\Item;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\BlockTransaction;
|
||||
use pocketmine\world\sound\ItemFrameAddItemSound;
|
||||
use pocketmine\world\sound\ItemFrameRemoveItemSound;
|
||||
use pocketmine\world\sound\ItemFrameRotateItemSound;
|
||||
use function is_infinite;
|
||||
use function is_nan;
|
||||
use function lcg_value;
|
||||
|
||||
class ItemFrame extends Flowable{
|
||||
use AnyFacingTrait;
|
||||
@ -154,7 +154,7 @@ class ItemFrame extends Flowable{
|
||||
return false;
|
||||
}
|
||||
$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->addSound($this->position, new ItemFrameRemoveItemSound());
|
||||
}
|
||||
@ -185,7 +185,7 @@ class ItemFrame extends Flowable{
|
||||
|
||||
public function getDropsForCompatibleTool(Item $item) : array{
|
||||
$drops = parent::getDropsForCompatibleTool($item);
|
||||
if($this->framedItem !== null && lcg_value() <= $this->itemDropChance){
|
||||
if($this->framedItem !== null && Utils::getRandomFloat() <= $this->itemDropChance){
|
||||
$drops[] = clone $this->framedItem;
|
||||
}
|
||||
|
||||
|
@ -33,9 +33,9 @@ use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Facing;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\sound\FizzSound;
|
||||
use pocketmine\world\sound\Sound;
|
||||
use function lcg_value;
|
||||
|
||||
abstract class Liquid extends Transparent{
|
||||
public const MAX_DECAY = 7;
|
||||
@ -368,7 +368,7 @@ abstract class Liquid extends Transparent{
|
||||
|
||||
protected function liquidCollide(Block $cause, Block $result) : bool{
|
||||
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;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class Magma extends Opaque{
|
||||
}
|
||||
|
||||
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);
|
||||
$entity->attack($ev);
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ use pocketmine\block\tile\MonsterSpawner as TileMonsterSpawner;
|
||||
use pocketmine\block\tile\NormalFurnace as TileNormalFurnace;
|
||||
use pocketmine\block\tile\Note as TileNote;
|
||||
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\Tile;
|
||||
use pocketmine\block\utils\AmethystTrait;
|
||||
@ -1359,8 +1360,8 @@ final class VanillaBlocks{
|
||||
WoodType::WARPED => VanillaItems::WARPED_SIGN(...),
|
||||
WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...),
|
||||
};
|
||||
self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem));
|
||||
self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall 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), TileSign::class);
|
||||
}
|
||||
}
|
||||
|
||||
|
60
src/command/ClosureCommand.php
Normal file
60
src/command/ClosureCommand.php
Normal 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);
|
||||
}
|
||||
}
|
@ -26,28 +26,28 @@ namespace pocketmine\command\defaults;
|
||||
use pocketmine\command\Command;
|
||||
use pocketmine\command\CommandSender;
|
||||
use pocketmine\command\utils\InvalidCommandSyntaxException;
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\permission\DefaultPermissionNames;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\scheduler\BulkCurlTask;
|
||||
use pocketmine\scheduler\BulkCurlTaskOperation;
|
||||
use pocketmine\timings\TimingsHandler;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\InternetException;
|
||||
use pocketmine\utils\InternetRequestResult;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\YmlServerProperties;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function count;
|
||||
use function fclose;
|
||||
use function file_exists;
|
||||
use function fopen;
|
||||
use function fseek;
|
||||
use function fwrite;
|
||||
use function http_build_query;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function json_decode;
|
||||
use function mkdir;
|
||||
use function stream_get_contents;
|
||||
use function strtolower;
|
||||
use const CURLOPT_AUTOREFERER;
|
||||
use const CURLOPT_FOLLOWLOCATION;
|
||||
@ -101,82 +101,91 @@ class TimingsCommand extends VanillaCommand{
|
||||
TimingsHandler::reload();
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_reset());
|
||||
}elseif($mode === "merged" || $mode === "report" || $paste){
|
||||
$timings = "";
|
||||
if($paste){
|
||||
$fileTimings = Utils::assumeNotFalse(fopen("php://temp", "r+b"), "Opening php://temp should never fail");
|
||||
}else{
|
||||
$index = 0;
|
||||
$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");
|
||||
|
||||
if(!file_exists($timingFolder)){
|
||||
mkdir($timingFolder, 0777);
|
||||
}
|
||||
$timings = Path::join($timingFolder, "timings.txt");
|
||||
while(file_exists($timings)){
|
||||
$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
|
||||
}
|
||||
|
||||
$fileTimings = fopen($timings, "a+b");
|
||||
}
|
||||
$lines = TimingsHandler::printTimings();
|
||||
foreach($lines as $line){
|
||||
fwrite($fileTimings, $line . PHP_EOL);
|
||||
}
|
||||
|
||||
if($paste){
|
||||
fseek($fileTimings, 0);
|
||||
$data = [
|
||||
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
|
||||
"data" => $content = stream_get_contents($fileTimings)
|
||||
];
|
||||
fclose($fileTimings);
|
||||
|
||||
$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
|
||||
|
||||
$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
|
||||
[new BulkCurlTaskOperation(
|
||||
"https://$host?upload=true",
|
||||
10,
|
||||
[],
|
||||
[
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"User-Agent: $agent",
|
||||
"Content-Type: application/x-www-form-urlencoded"
|
||||
],
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => http_build_query($data),
|
||||
CURLOPT_AUTOREFERER => false,
|
||||
CURLOPT_FOLLOWLOCATION => false
|
||||
]
|
||||
)],
|
||||
function(array $results) use ($sender, $host) : void{
|
||||
/** @phpstan-var array<InternetRequestResult|InternetException> $results */
|
||||
if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
|
||||
return;
|
||||
}
|
||||
$result = $results[0];
|
||||
if($result instanceof InternetException){
|
||||
$sender->getServer()->getLogger()->logException($result);
|
||||
return;
|
||||
}
|
||||
$response = json_decode($result->getBody(), true);
|
||||
if(is_array($response) && isset($response["id"])){
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
|
||||
"https://" . $host . "/?id=" . $response["id"]));
|
||||
}else{
|
||||
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
|
||||
}
|
||||
}
|
||||
));
|
||||
}else{
|
||||
fclose($fileTimings);
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
|
||||
}
|
||||
$timingsPromise = TimingsHandler::requestPrintTimings();
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_collect());
|
||||
$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{
|
||||
throw new InvalidCommandSyntaxException();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $lines
|
||||
* @phpstan-param list<string> $lines
|
||||
*/
|
||||
private function createReportFile(array $lines, CommandSender $sender) : void{
|
||||
$index = 0;
|
||||
$timingFolder = Path::join($sender->getServer()->getDataPath(), "timings");
|
||||
|
||||
if(!file_exists($timingFolder)){
|
||||
mkdir($timingFolder, 0777);
|
||||
}
|
||||
$timings = Path::join($timingFolder, "timings.txt");
|
||||
while(file_exists($timings)){
|
||||
$timings = Path::join($timingFolder, "timings" . (++$index) . ".txt");
|
||||
}
|
||||
|
||||
$fileTimings = ErrorToExceptionHandler::trapAndRemoveFalse(fn() => fopen($timings, "a+b"));
|
||||
foreach($lines as $line){
|
||||
fwrite($fileTimings, $line . PHP_EOL);
|
||||
}
|
||||
fclose($fileTimings);
|
||||
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsWrite($timings));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $lines
|
||||
* @phpstan-param list<string> $lines
|
||||
*/
|
||||
private function uploadReport(array $lines, CommandSender $sender) : void{
|
||||
$data = [
|
||||
"browser" => $agent = $sender->getServer()->getName() . " " . $sender->getServer()->getPocketMineVersion(),
|
||||
"data" => implode("\n", $lines)
|
||||
];
|
||||
|
||||
$host = $sender->getServer()->getConfigGroup()->getPropertyString(YmlServerProperties::TIMINGS_HOST, "timings.pmmp.io");
|
||||
|
||||
$sender->getServer()->getAsyncPool()->submitTask(new BulkCurlTask(
|
||||
[new BulkCurlTaskOperation(
|
||||
"https://$host?upload=true",
|
||||
10,
|
||||
[],
|
||||
[
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"User-Agent: $agent",
|
||||
"Content-Type: application/x-www-form-urlencoded"
|
||||
],
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => http_build_query($data),
|
||||
CURLOPT_AUTOREFERER => false,
|
||||
CURLOPT_FOLLOWLOCATION => false
|
||||
]
|
||||
)],
|
||||
function(array $results) use ($sender, $host) : void{
|
||||
/** @phpstan-var array<InternetRequestResult|InternetException> $results */
|
||||
if($sender instanceof Player && !$sender->isOnline()){ // TODO replace with a more generic API method for checking availability of CommandSender
|
||||
return;
|
||||
}
|
||||
$result = $results[0];
|
||||
if($result instanceof InternetException){
|
||||
$sender->getServer()->getLogger()->logException($result);
|
||||
return;
|
||||
}
|
||||
$response = json_decode($result->getBody(), true);
|
||||
if(is_array($response) && isset($response["id"])){
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
|
||||
"https://" . $host . "/?id=" . $response["id"]));
|
||||
}else{
|
||||
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ final class BedrockDataFiles{
|
||||
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 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 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';
|
||||
|
@ -122,4 +122,5 @@ final class BiomeIds{
|
||||
public const DEEP_DARK = 190;
|
||||
public const MANGROVE_SWAMP = 191;
|
||||
public const CHERRY_GROVE = 192;
|
||||
public const PALE_GARDEN = 193;
|
||||
}
|
||||
|
@ -66,5 +66,7 @@ final class EnchantmentIdMap{
|
||||
$this->register(EnchantmentIds::VANISHING, VanillaEnchantments::VANISHING());
|
||||
|
||||
$this->register(EnchantmentIds::SWIFT_SNEAK, VanillaEnchantments::SWIFT_SNEAK());
|
||||
|
||||
$this->register(EnchantmentIds::FROST_WALKER, VanillaEnchantments::FROST_WALKER());
|
||||
}
|
||||
}
|
||||
|
@ -93,12 +93,17 @@ final class BlockStateNames{
|
||||
public const MC_VERTICAL_HALF = "minecraft:vertical_half";
|
||||
public const MOISTURIZED_AMOUNT = "moisturized_amount";
|
||||
public const MULTI_FACE_DIRECTION_BITS = "multi_face_direction_bits";
|
||||
public const NATURAL = "natural";
|
||||
public const OCCUPIED_BIT = "occupied_bit";
|
||||
public const OMINOUS = "ominous";
|
||||
public const OPEN_BIT = "open_bit";
|
||||
public const ORIENTATION = "orientation";
|
||||
public const OUTPUT_LIT_BIT = "output_lit_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 PILLAR_AXIS = "pillar_axis";
|
||||
public const PORTAL_AXIS = "portal_axis";
|
||||
@ -116,6 +121,7 @@ final class BlockStateNames{
|
||||
public const STABILITY_CHECK = "stability_check";
|
||||
public const STRUCTURE_BLOCK_TYPE = "structure_block_type";
|
||||
public const SUSPENDED_BIT = "suspended_bit";
|
||||
public const TIP = "tip";
|
||||
public const TOGGLE_BIT = "toggle_bit";
|
||||
public const TORCH_FACING_DIRECTION = "torch_facing_direction";
|
||||
public const TRIAL_SPAWNER_STATE = "trial_spawner_state";
|
||||
|
@ -106,6 +106,22 @@ final class BlockStateStringValues{
|
||||
public const ORIENTATION_UP_WEST = "up_west";
|
||||
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_Y = "y";
|
||||
public const PILLAR_AXIS_Z = "z";
|
||||
|
@ -192,6 +192,7 @@ final class BlockTypeNames{
|
||||
public const CAVE_VINES_HEAD_WITH_BERRIES = "minecraft:cave_vines_head_with_berries";
|
||||
public const CHAIN = "minecraft:chain";
|
||||
public const CHAIN_COMMAND_BLOCK = "minecraft:chain_command_block";
|
||||
public const CHALKBOARD = "minecraft:chalkboard";
|
||||
public const CHEMICAL_HEAT = "minecraft:chemical_heat";
|
||||
public const CHERRY_BUTTON = "minecraft:cherry_button";
|
||||
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_QUARTZ_BLOCK = "minecraft:chiseled_quartz_block";
|
||||
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_STONE_BRICKS = "minecraft:chiseled_stone_bricks";
|
||||
public const CHISELED_TUFF = "minecraft:chiseled_tuff";
|
||||
@ -227,6 +229,7 @@ final class BlockTypeNames{
|
||||
public const CHORUS_PLANT = "minecraft:chorus_plant";
|
||||
public const CLAY = "minecraft:clay";
|
||||
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_ORE = "minecraft:coal_ore";
|
||||
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 CRAFTER = "minecraft:crafter";
|
||||
public const CRAFTING_TABLE = "minecraft:crafting_table";
|
||||
public const CREAKING_HEART = "minecraft:creaking_heart";
|
||||
public const CREEPER_HEAD = "minecraft:creeper_head";
|
||||
public const CRIMSON_BUTTON = "minecraft:crimson_button";
|
||||
public const CRIMSON_DOOR = "minecraft:crimson_door";
|
||||
@ -831,6 +835,7 @@ final class BlockTypeNames{
|
||||
public const OBSERVER = "minecraft:observer";
|
||||
public const OBSIDIAN = "minecraft:obsidian";
|
||||
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_CAKE = "minecraft:orange_candle_cake";
|
||||
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 PACKED_ICE = "minecraft:packed_ice";
|
||||
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 PEONY = "minecraft:peony";
|
||||
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 REPEATING_COMMAND_BLOCK = "minecraft:repeating_command_block";
|
||||
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 ROSE_BUSH = "minecraft:rose_bush";
|
||||
public const SAND = "minecraft:sand";
|
||||
@ -1096,6 +1128,8 @@ final class BlockTypeNames{
|
||||
public const STRIPPED_MANGROVE_WOOD = "minecraft:stripped_mangrove_wood";
|
||||
public const STRIPPED_OAK_LOG = "minecraft:stripped_oak_log";
|
||||
public const STRIPPED_OAK_WOOD = "minecraft:stripped_oak_wood";
|
||||
public const STRIPPED_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_WOOD = "minecraft:stripped_spruce_wood";
|
||||
public const STRIPPED_WARPED_HYPHAE = "minecraft:stripped_warped_hyphae";
|
||||
|
@ -352,6 +352,7 @@ final class ItemSerializerDeserializerRegistrar{
|
||||
$this->map1to1Item(Ids::RAW_COPPER, Items::RAW_COPPER());
|
||||
$this->map1to1Item(Ids::RAW_GOLD, Items::RAW_GOLD());
|
||||
$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::RIB_ARMOR_TRIM_SMITHING_TEMPLATE, Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
|
||||
$this->map1to1Item(Ids::ROTTEN_FLESH, Items::ROTTEN_FLESH());
|
||||
|
@ -75,6 +75,7 @@ final class ItemTypeNames{
|
||||
public const BLEACH = "minecraft:bleach";
|
||||
public const BLUE_BUNDLE = "minecraft:blue_bundle";
|
||||
public const BLUE_DYE = "minecraft:blue_dye";
|
||||
public const BOARD = "minecraft:board";
|
||||
public const BOAT = "minecraft:boat";
|
||||
public const BOGGED_SPAWN_EGG = "minecraft:bogged_spawn_egg";
|
||||
public const BOLT_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:bolt_armor_trim_smithing_template";
|
||||
@ -154,6 +155,7 @@ final class ItemTypeNames{
|
||||
public const CORAL_FAN = "minecraft:coral_fan";
|
||||
public const CORAL_FAN_DEAD = "minecraft:coral_fan_dead";
|
||||
public const COW_SPAWN_EGG = "minecraft:cow_spawn_egg";
|
||||
public const CREAKING_SPAWN_EGG = "minecraft:creaking_spawn_egg";
|
||||
public const CREEPER_BANNER_PATTERN = "minecraft:creeper_banner_pattern";
|
||||
public const CREEPER_SPAWN_EGG = "minecraft:creeper_spawn_egg";
|
||||
public const CRIMSON_DOOR = "minecraft:crimson_door";
|
||||
@ -398,6 +400,11 @@ final class ItemTypeNames{
|
||||
public const ORANGE_DYE = "minecraft:orange_dye";
|
||||
public const OXIDIZED_COPPER_DOOR = "minecraft:oxidized_copper_door";
|
||||
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 PAPER = "minecraft:paper";
|
||||
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 REDSTONE = "minecraft:redstone";
|
||||
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 ROTTEN_FLESH = "minecraft:rotten_flesh";
|
||||
public const SADDLE = "minecraft:saddle";
|
||||
|
@ -75,7 +75,6 @@ use function deg2rad;
|
||||
use function floor;
|
||||
use function fmod;
|
||||
use function get_class;
|
||||
use function lcg_value;
|
||||
use function sin;
|
||||
use function spl_object_id;
|
||||
use const M_PI_2;
|
||||
@ -910,7 +909,7 @@ abstract class Entity{
|
||||
return false;
|
||||
}
|
||||
|
||||
$force = lcg_value() * 0.2 + 0.1;
|
||||
$force = Utils::getRandomFloat() * 0.2 + 0.1;
|
||||
|
||||
$this->motion = match($direction){
|
||||
Facing::WEST => $this->motion->withComponents(-$force, null, null),
|
||||
|
@ -34,6 +34,7 @@ interface FoodSource extends Consumable{
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
|
||||
use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\world\sound\TotemUseSound;
|
||||
use pocketmine\world\World;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use function array_fill;
|
||||
@ -189,8 +190,16 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
||||
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{
|
||||
if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->hungerManager->isHungry()){
|
||||
if($consumable instanceof FoodSource && $consumable->requiresHunger() && !$this->canEat()){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
return $this->getFood() < $this->getMaxFood();
|
||||
|
@ -25,6 +25,8 @@ namespace pocketmine\entity;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockTypeIds;
|
||||
use pocketmine\block\VanillaBlocks;
|
||||
use pocketmine\block\Water;
|
||||
use pocketmine\data\bedrock\EffectIdMap;
|
||||
use pocketmine\entity\animation\DeathAnimation;
|
||||
use pocketmine\entity\animation\HurtAnimation;
|
||||
@ -44,6 +46,7 @@ use pocketmine\item\Durable;
|
||||
use pocketmine\item\enchantment\Enchantment;
|
||||
use pocketmine\item\enchantment\VanillaEnchantments;
|
||||
use pocketmine\item\Item;
|
||||
use pocketmine\math\AxisAlignedBB;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\math\VoxelRayTrace;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
@ -58,18 +61,19 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\sound\BurpSound;
|
||||
use pocketmine\world\sound\EntityLandSound;
|
||||
use pocketmine\world\sound\EntityLongFallSound;
|
||||
use pocketmine\world\sound\EntityShortFallSound;
|
||||
use pocketmine\world\sound\ItemBreakSound;
|
||||
use function abs;
|
||||
use function array_shift;
|
||||
use function atan2;
|
||||
use function ceil;
|
||||
use function count;
|
||||
use function floor;
|
||||
use function ksort;
|
||||
use function lcg_value;
|
||||
use function max;
|
||||
use function min;
|
||||
use function mt_getrandmax;
|
||||
@ -128,6 +132,8 @@ abstract class Living extends Entity{
|
||||
protected bool $gliding = false;
|
||||
protected bool $swimming = false;
|
||||
|
||||
private ?int $frostWalkerLevel = null;
|
||||
|
||||
protected function getInitialDragMultiplier() : float{ return 0.02; }
|
||||
|
||||
protected function getInitialGravity() : float{ return 0.08; }
|
||||
@ -151,6 +157,14 @@ abstract class Living extends Entity{
|
||||
$this->getViewers(),
|
||||
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();
|
||||
|
||||
@ -490,7 +504,7 @@ abstract class Living extends Entity{
|
||||
$helmet = $this->armorInventory->getHelmet();
|
||||
if($helmet instanceof Armor){
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@ -687,6 +701,47 @@ abstract class Living extends Entity{
|
||||
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.
|
||||
*/
|
||||
@ -697,7 +752,7 @@ abstract class Living extends Entity{
|
||||
$this->setBreathing(false);
|
||||
|
||||
if(($respirationLevel = $this->armorInventory->getHelmet()->getEnchantmentLevel(VanillaEnchantments::RESPIRATION())) <= 0 ||
|
||||
lcg_value() <= (1 / ($respirationLevel + 1))
|
||||
Utils::getRandomFloat() <= (1 / ($respirationLevel + 1))
|
||||
){
|
||||
$ticks -= $tickDiff;
|
||||
if($ticks <= -20){
|
||||
|
@ -33,7 +33,7 @@ use pocketmine\nbt\tag\CompoundTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Binary;
|
||||
use function lcg_value;
|
||||
use pocketmine\utils\Utils;
|
||||
use function mt_rand;
|
||||
|
||||
class Armor extends Durable{
|
||||
@ -129,7 +129,7 @@ class Armor extends Durable{
|
||||
|
||||
$chance = 1 / ($unbreakingLevel + 1);
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace pocketmine\item;
|
||||
|
||||
use pocketmine\item\enchantment\VanillaEnchantments;
|
||||
use pocketmine\nbt\tag\CompoundTag;
|
||||
use function lcg_value;
|
||||
use pocketmine\utils\Utils;
|
||||
use function min;
|
||||
|
||||
abstract class Durable extends Item{
|
||||
@ -87,7 +87,7 @@ abstract class Durable extends Item{
|
||||
|
||||
$chance = 1 / ($unbreakingLevel + 1);
|
||||
for($i = 0; $i < $amount; ++$i){
|
||||
if(lcg_value() > $chance){
|
||||
if(Utils::getRandomFloat() > $chance){
|
||||
$negated++;
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,6 @@ abstract class Food extends Item implements FoodSourceItem{
|
||||
}
|
||||
|
||||
public function canStartUsingItem(Player $player) : bool{
|
||||
return !$this->requiresHunger() || $player->getHungerManager()->isHungry();
|
||||
return !$this->requiresHunger() || $player->canEat();
|
||||
}
|
||||
}
|
||||
|
@ -327,8 +327,9 @@ final class ItemTypeIds{
|
||||
public const GOAT_HORN = 20288;
|
||||
public const END_CRYSTAL = 20289;
|
||||
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;
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace pocketmine\item;
|
||||
|
||||
use pocketmine\entity\effect\EffectInstance;
|
||||
use pocketmine\entity\effect\VanillaEffects;
|
||||
use function lcg_value;
|
||||
use pocketmine\utils\Utils;
|
||||
|
||||
class RottenFlesh extends Food{
|
||||
|
||||
@ -38,7 +38,7 @@ class RottenFlesh extends Food{
|
||||
}
|
||||
|
||||
public function getAdditionalEffects() : array{
|
||||
if(lcg_value() <= 0.8){
|
||||
if(Utils::getRandomFloat() <= 0.8){
|
||||
return [
|
||||
new EffectInstance(VanillaEffects::HUNGER(), 600)
|
||||
];
|
||||
|
@ -27,15 +27,15 @@ use pocketmine\block\Block;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\math\Vector3;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\World;
|
||||
use function lcg_value;
|
||||
|
||||
abstract class SpawnEgg extends Item{
|
||||
|
||||
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{
|
||||
$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()){
|
||||
$entity->setNameTag($this->getCustomName());
|
||||
|
@ -1481,6 +1481,7 @@ final class StringToItemParser extends StringToTParser{
|
||||
$result->register("record_strad", fn() => Items::RECORD_STRAD());
|
||||
$result->register("record_wait", fn() => Items::RECORD_WAIT());
|
||||
$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_dust", fn() => Items::REDSTONE_DUST());
|
||||
$result->register("rib_armor_trim_smithing_template", fn() => Items::RIB_ARMOR_TRIM_SMITHING_TEMPLATE());
|
||||
|
@ -284,6 +284,7 @@ use function strtolower;
|
||||
* @method static Record RECORD_STRAD()
|
||||
* @method static Record RECORD_WAIT()
|
||||
* @method static Record RECORD_WARD()
|
||||
* @method static Item RECOVERY_COMPASS()
|
||||
* @method static Redstone REDSTONE_DUST()
|
||||
* @method static Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE()
|
||||
* @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_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("recovery_compass", fn(IID $id) => new Item($id, "Recovery Compass"));
|
||||
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("scute", fn(IID $id) => new Item($id, "Scute"));
|
||||
|
@ -57,6 +57,7 @@ final class AvailableEnchantmentRegistry{
|
||||
$this->register(Enchantments::THORNS(), [Tags::CHESTPLATE], [Tags::HELMET, Tags::LEGGINGS, Tags::BOOTS]);
|
||||
$this->register(Enchantments::RESPIRATION(), [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::KNOCKBACK(), [Tags::SWORD], []);
|
||||
$this->register(Enchantments::FIRE_ASPECT(), [Tags::SWORD], []);
|
||||
|
@ -44,6 +44,7 @@ final class StringToEnchantmentParser extends StringToTParser{
|
||||
$result->register("fire_protection", fn() => VanillaEnchantments::FIRE_PROTECTION());
|
||||
$result->register("flame", fn() => VanillaEnchantments::FLAME());
|
||||
$result->register("fortune", fn() => VanillaEnchantments::FORTUNE());
|
||||
$result->register("frost_walker", fn() => VanillaEnchantments::FROST_WALKER());
|
||||
$result->register("infinity", fn() => VanillaEnchantments::INFINITY());
|
||||
$result->register("knockback", fn() => VanillaEnchantments::KNOCKBACK());
|
||||
$result->register("mending", fn() => VanillaEnchantments::MENDING());
|
||||
|
@ -41,6 +41,7 @@ use pocketmine\utils\RegistryTrait;
|
||||
* @method static ProtectionEnchantment FIRE_PROTECTION()
|
||||
* @method static Enchantment FLAME()
|
||||
* @method static Enchantment FORTUNE()
|
||||
* @method static Enchantment FROST_WALKER()
|
||||
* @method static Enchantment INFINITY()
|
||||
* @method static KnockbackEnchantment KNOCKBACK()
|
||||
* @method static Enchantment MENDING()
|
||||
@ -131,6 +132,14 @@ final class VanillaEnchantments{
|
||||
fn(int $level) : int => 10 * $level,
|
||||
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(
|
||||
KnownTranslationFactory::enchantment_waterWorker(),
|
||||
Rarity::RARE,
|
||||
|
@ -1441,6 +1441,10 @@ final class KnownTranslationFactory{
|
||||
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{
|
||||
return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_TIMINGS_DESCRIPTION, []);
|
||||
}
|
||||
|
@ -315,6 +315,7 @@ final class KnownTranslationKeys{
|
||||
public const POCKETMINE_COMMAND_TIME_DESCRIPTION = "pocketmine.command.time.description";
|
||||
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_COLLECT = "pocketmine.command.timings.collect";
|
||||
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_ENABLE = "pocketmine.command.timings.enable";
|
||||
|
@ -77,6 +77,7 @@ use pocketmine\network\mcpe\protocol\PlayerHotbarPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerInputPacket;
|
||||
use pocketmine\network\mcpe\protocol\PlayerSkinPacket;
|
||||
use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket;
|
||||
use pocketmine\network\mcpe\protocol\serializer\BitSet;
|
||||
use pocketmine\network\mcpe\protocol\ServerSettingsRequestPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket;
|
||||
@ -135,7 +136,7 @@ class InGamePacketHandler extends PacketHandler{
|
||||
protected ?Vector3 $lastPlayerAuthInputPosition = null;
|
||||
protected ?float $lastPlayerAuthInputYaw = null;
|
||||
protected ?float $lastPlayerAuthInputPitch = null;
|
||||
protected ?int $lastPlayerAuthInputFlags = null;
|
||||
protected ?BitSet $lastPlayerAuthInputFlags = null;
|
||||
|
||||
public bool $forceMoveSync = false;
|
||||
|
||||
@ -161,9 +162,9 @@ class InGamePacketHandler extends PacketHandler{
|
||||
return true;
|
||||
}
|
||||
|
||||
private function resolveOnOffInputFlags(int $inputFlags, int $startFlag, int $stopFlag) : ?bool{
|
||||
$enabled = ($inputFlags & (1 << $startFlag)) !== 0;
|
||||
$disabled = ($inputFlags & (1 << $stopFlag)) !== 0;
|
||||
private function resolveOnOffInputFlags(BitSet $inputFlags, int $startFlag, int $stopFlag) : ?bool{
|
||||
$enabled = $inputFlags->get($startFlag);
|
||||
$disabled = $inputFlags->get($stopFlag);
|
||||
if($enabled !== $disabled){
|
||||
return $enabled;
|
||||
}
|
||||
@ -215,7 +216,10 @@ class InGamePacketHandler extends PacketHandler{
|
||||
if($inputFlags !== $this->lastPlayerAuthInputFlags){
|
||||
$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);
|
||||
$swimming = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_SWIMMING, PlayerAuthInputFlags::STOP_SWIMMING);
|
||||
$gliding = $this->resolveOnOffInputFlags($inputFlags, PlayerAuthInputFlags::START_GLIDING, PlayerAuthInputFlags::STOP_GLIDING);
|
||||
@ -230,10 +234,10 @@ class InGamePacketHandler extends PacketHandler{
|
||||
$this->player->sendData([$this->player]);
|
||||
}
|
||||
|
||||
if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){
|
||||
if($inputFlags->get(PlayerAuthInputFlags::START_JUMPING)){
|
||||
$this->player->jump();
|
||||
}
|
||||
if($packet->hasFlag(PlayerAuthInputFlags::MISSED_SWING)){
|
||||
if($inputFlags->get(PlayerAuthInputFlags::MISSED_SWING)){
|
||||
$this->player->missSwing();
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ final class ItemStackResponseBuilder{
|
||||
$item->getCount(),
|
||||
$itemStackInfo->getStackId(),
|
||||
$item->getCustomName(),
|
||||
$item->getCustomName(),
|
||||
$item instanceof Durable ? $item->getDamage() : 0,
|
||||
);
|
||||
}
|
||||
|
@ -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\ResourcePackType;
|
||||
use pocketmine\resourcepacks\ResourcePack;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function ceil;
|
||||
@ -103,7 +104,7 @@ class ResourcePacksPacketHandler extends PacketHandler{
|
||||
//TODO: more stuff
|
||||
|
||||
return new ResourcePackInfoEntry(
|
||||
$pack->getPackId(),
|
||||
Uuid::fromString($pack->getPackId()),
|
||||
$pack->getPackVersion(),
|
||||
$pack->getPackSize(),
|
||||
$this->encryptionKeys[$pack->getPackId()] ?? "",
|
||||
@ -117,7 +118,9 @@ class ResourcePacksPacketHandler extends PacketHandler{
|
||||
resourcePackEntries: $resourcePackEntries,
|
||||
mustAccept: $this->mustAccept,
|
||||
hasAddons: false,
|
||||
hasScripts: false
|
||||
hasScripts: false,
|
||||
worldTemplateId: Uuid::fromString(Uuid::NIL),
|
||||
worldTemplateVersion: ""
|
||||
));
|
||||
$this->session->getLogger()->debug("Waiting for client to accept resource packs");
|
||||
}
|
||||
|
@ -109,6 +109,7 @@ use pocketmine\network\mcpe\protocol\AnimatePacket;
|
||||
use pocketmine\network\mcpe\protocol\MovePlayerPacket;
|
||||
use pocketmine\network\mcpe\protocol\SetActorMotionPacket;
|
||||
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\EntityMetadataFlags;
|
||||
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_Y = "SpawnY"; //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_LAST_KNOWN_XUID = "LastKnownXUID"; //TAG_String
|
||||
|
||||
@ -272,6 +277,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
private bool $respawnLocked = false;
|
||||
|
||||
private ?Position $deathPosition = null;
|
||||
|
||||
//TODO: Abilities
|
||||
protected bool $autoJump = true;
|
||||
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){
|
||||
$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{
|
||||
@ -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
|
||||
*/
|
||||
@ -1474,6 +1508,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canEat() : bool{
|
||||
return $this->isCreative() || parent::canEat();
|
||||
}
|
||||
|
||||
public function canBreathe() : bool{
|
||||
return $this->isCreative() || parent::canBreathe();
|
||||
}
|
||||
@ -2333,6 +2371,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
protected function destroyCycles() : void{
|
||||
$this->networkSession = null;
|
||||
$this->spawnPosition = null;
|
||||
$this->deathPosition = null;
|
||||
$this->blockBreakHandler = null;
|
||||
parent::destroyCycles();
|
||||
}
|
||||
@ -2374,6 +2413,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
$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->setLong(self::TAG_FIRST_PLAYED, (int) $this->firstPlayed->format('Uv'));
|
||||
$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.
|
||||
$this->removeCurrentWindow();
|
||||
|
||||
$this->setDeathPosition($this->getPosition());
|
||||
|
||||
$ev = new PlayerDeathEvent($this, $this->getDrops(), $this->getXpDropAmount(), null);
|
||||
$ev->call();
|
||||
|
||||
@ -2521,6 +2569,17 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
$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));
|
||||
|
||||
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{
|
||||
|
@ -27,6 +27,7 @@ use pmmp\thread\Runnable;
|
||||
use pmmp\thread\ThreadSafe;
|
||||
use pmmp\thread\ThreadSafeArray;
|
||||
use pocketmine\thread\NonThreadSafeValue;
|
||||
use pocketmine\timings\Timings;
|
||||
use function array_key_exists;
|
||||
use function igbinary_serialize;
|
||||
use function igbinary_unserialize;
|
||||
@ -67,7 +68,10 @@ abstract class AsyncTask extends Runnable{
|
||||
*/
|
||||
private static array $threadLocalStorage = [];
|
||||
|
||||
/** @phpstan-var ThreadSafeArray<int, string>|null */
|
||||
/**
|
||||
* @phpstan-var ThreadSafeArray<int, string>|null
|
||||
* @deprecated
|
||||
*/
|
||||
private ?ThreadSafeArray $progressUpdates = null;
|
||||
|
||||
private ThreadSafe|string|int|bool|null|float $result = null;
|
||||
@ -78,7 +82,14 @@ abstract class AsyncTask extends Runnable{
|
||||
public function run() : void{
|
||||
$this->result = null;
|
||||
|
||||
$this->onRun();
|
||||
$timings = Timings::getAsyncTaskRunTimings($this);
|
||||
$timings->startTiming();
|
||||
|
||||
try{
|
||||
$this->onRun();
|
||||
}finally{
|
||||
$timings->stopTiming();
|
||||
}
|
||||
|
||||
$this->finished = true;
|
||||
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
|
||||
* {@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
|
||||
*/
|
||||
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.
|
||||
* All {@link AsyncTask::publishProgress} calls should result in {@link AsyncTask::onProgressUpdate} calls before
|
||||
* {@link AsyncTask::onCompletion} is called.
|
||||
|
60
src/scheduler/TimingsCollectionTask.php
Normal file
60
src/scheduler/TimingsCollectionTask.php
Normal 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);
|
||||
}
|
||||
}
|
60
src/scheduler/TimingsControlTask.php
Normal file
60
src/scheduler/TimingsControlTask.php
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@ use function str_starts_with;
|
||||
|
||||
abstract class Timings{
|
||||
public const GROUP_MINECRAFT = "Minecraft";
|
||||
/** @deprecated No longer used */
|
||||
public const GROUP_BREAKDOWN = "Minecraft - Breakdown";
|
||||
|
||||
private static bool $initialized = false;
|
||||
@ -124,11 +125,16 @@ abstract class Timings{
|
||||
|
||||
/** @var TimingsHandler[] */
|
||||
private static array $asyncTaskProgressUpdate = [];
|
||||
|
||||
/** @var TimingsHandler[] */
|
||||
private static array $asyncTaskCompletion = [];
|
||||
/** @var TimingsHandler[] */
|
||||
private static array $asyncTaskError = [];
|
||||
|
||||
private static TimingsHandler $asyncTaskWorkers;
|
||||
/** @var TimingsHandler[] */
|
||||
private static array $asyncTaskRun = [];
|
||||
|
||||
public static function init() : void{
|
||||
if(self::$initialized){
|
||||
return;
|
||||
@ -188,6 +194,8 @@ abstract class Timings{
|
||||
self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion 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::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache");
|
||||
|
||||
@ -344,6 +352,9 @@ abstract class Timings{
|
||||
return self::$asyncTaskCompletion[$taskClass];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated No longer used
|
||||
*/
|
||||
public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_MINECRAFT) : TimingsHandler{
|
||||
$taskClass = $task::class;
|
||||
if(!isset(self::$asyncTaskError[$taskClass])){
|
||||
@ -357,4 +368,18 @@ abstract class Timings{
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
@ -23,20 +23,66 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\timings;
|
||||
|
||||
use pmmp\thread\Thread as NativeThread;
|
||||
use pocketmine\promise\Promise;
|
||||
use pocketmine\promise\PromiseResolver;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use pocketmine\utils\Utils;
|
||||
use function array_merge;
|
||||
use function array_push;
|
||||
use function hrtime;
|
||||
use function implode;
|
||||
use function spl_object_id;
|
||||
|
||||
/**
|
||||
* @phpstan-type CollectPromise Promise<list<string>>
|
||||
*/
|
||||
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 int $timingStart = 0;
|
||||
|
||||
/** @return string[] */
|
||||
public static function printTimings() : array{
|
||||
/** @phpstan-var ObjectSet<\Closure(bool $enable) : void> */
|
||||
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 = [];
|
||||
|
||||
foreach(TimingsRecord::getAll() as $timings){
|
||||
@ -49,7 +95,7 @@ class TimingsHandler{
|
||||
|
||||
$avg = $time / $count;
|
||||
|
||||
$group = $timings->getGroup();
|
||||
$group = $timings->getGroup() . ($threadId !== null ? " ThreadId: $threadId" : "");
|
||||
$groups[$group][] = implode(" ", [
|
||||
$timings->getName(),
|
||||
"Time: $time",
|
||||
@ -72,6 +118,15 @@ class TimingsHandler{
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function printFooter() : array{
|
||||
$result = [];
|
||||
|
||||
$result[] = "# Version " . Server::getInstance()->getVersion();
|
||||
$result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion();
|
||||
|
||||
@ -79,29 +134,95 @@ class TimingsHandler{
|
||||
|
||||
$sampleTime = hrtime(true) - self::$timingStart;
|
||||
$result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)";
|
||||
|
||||
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{
|
||||
return self::$enabled;
|
||||
}
|
||||
|
||||
public static function setEnabled(bool $enable = true) : void{
|
||||
if($enable === self::$enabled){
|
||||
return;
|
||||
}
|
||||
self::$enabled = $enable;
|
||||
self::reload();
|
||||
self::internalReload();
|
||||
if(self::$toggleCallbacks !== null){
|
||||
foreach(self::$toggleCallbacks as $callback){
|
||||
$callback($enable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getStartTime() : float{
|
||||
return self::$timingStart;
|
||||
}
|
||||
|
||||
public static function reload() : void{
|
||||
private static function internalReload() : void{
|
||||
TimingsRecord::reset();
|
||||
if(self::$enabled){
|
||||
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{
|
||||
if(self::$enabled){
|
||||
TimingsRecord::tick($measure);
|
||||
|
@ -67,6 +67,8 @@ use function is_nan;
|
||||
use function is_object;
|
||||
use function is_string;
|
||||
use function mb_check_encoding;
|
||||
use function mt_getrandmax;
|
||||
use function mt_rand;
|
||||
use function ob_end_clean;
|
||||
use function ob_get_contents;
|
||||
use function ob_start;
|
||||
@ -688,4 +690,12 @@ final class Utils{
|
||||
//jit not available
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +83,7 @@ use pocketmine\ServerConfigGroup;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Limits;
|
||||
use pocketmine\utils\ReversePriorityQueue;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\biome\Biome;
|
||||
use pocketmine\world\biome\BiomeRegistry;
|
||||
use pocketmine\world\format\Chunk;
|
||||
@ -120,7 +121,6 @@ use function get_class;
|
||||
use function gettype;
|
||||
use function is_a;
|
||||
use function is_object;
|
||||
use function lcg_value;
|
||||
use function max;
|
||||
use function microtime;
|
||||
use function min;
|
||||
@ -1998,10 +1998,10 @@ class World implements ChunkManager{
|
||||
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->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();
|
||||
|
||||
return $itemEntity;
|
||||
@ -2018,9 +2018,9 @@ class World implements ChunkManager{
|
||||
$orbs = [];
|
||||
|
||||
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();
|
||||
|
||||
$orbs[] = $orb;
|
||||
|
@ -90,6 +90,11 @@ parameters:
|
||||
count: 1
|
||||
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\\.$#"
|
||||
count: 1
|
||||
|
@ -27,6 +27,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use function get_debug_type;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_int;
|
||||
@ -95,11 +96,12 @@ class BlockTest extends TestCase{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
* @phpstan-return array<string, int>
|
||||
* @return int[][]|string[][]
|
||||
* @phpstan-return array{array<string, int>, array<string, string>}
|
||||
*/
|
||||
public static function computeConsistencyCheckTable(RuntimeBlockStateRegistry $blockStateRegistry) : array{
|
||||
$newTable = [];
|
||||
$newTileMap = [];
|
||||
|
||||
$idNameLookup = [];
|
||||
//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()];
|
||||
$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[]
|
||||
*/
|
||||
public static function computeConsistencyCheckDiff(string $expectedFile, array $actual) : array{
|
||||
$expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 2, JSON_THROW_ON_ERROR);
|
||||
public static function computeConsistencyCheckDiff(string $expectedFile, array $actualStateCounts, array $actualTiles) : array{
|
||||
$expected = json_decode(Filesystem::fileGetContents($expectedFile), true, 3, JSON_THROW_ON_ERROR);
|
||||
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 = [];
|
||||
foreach(Utils::promoteKeys($expected) as $typeName => $numStates){
|
||||
foreach(Utils::promoteKeys($expectedStates) as $typeName => $numStates){
|
||||
if(!is_string($typeName) || !is_int($numStates)){
|
||||
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)";
|
||||
}elseif($actual[$typeName] !== $numStates){
|
||||
$errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actual[$typeName];
|
||||
}elseif($actualStateCounts[$typeName] !== $numStates){
|
||||
$errors[] = "Block type $typeName permutation count changed: $numStates -> " . $actualStateCounts[$typeName];
|
||||
}
|
||||
}
|
||||
foreach(Utils::stringifyKeys($actual) as $typeName => $numStates){
|
||||
if(!isset($expected[$typeName])){
|
||||
$errors[] = "Added block type $typeName (" . $actual[$typeName] . " permutations)";
|
||||
foreach(Utils::stringifyKeys($actualStateCounts) as $typeName => $numStates){
|
||||
if(!isset($expectedStates[$typeName])){
|
||||
$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{
|
||||
$newTable = self::computeConsistencyCheckTable($this->blockFactory);
|
||||
$errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable);
|
||||
[$newTable, $newTileMap] = self::computeConsistencyCheckTable($this->blockFactory);
|
||||
$errors = self::computeConsistencyCheckDiff(__DIR__ . '/block_factory_consistency_check.json', $newTable, $newTileMap);
|
||||
|
||||
self::assertEmpty($errors, "Block factory consistency check failed:\n" . implode("\n", $errors));
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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). */
|
||||
|
||||
$newTable = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance());
|
||||
[$newTable, $newTiles] = BlockTest::computeConsistencyCheckTable(RuntimeBlockStateRegistry::getInstance());
|
||||
|
||||
$oldTablePath = __DIR__ . '/block_factory_consistency_check.json';
|
||||
if(file_exists($oldTablePath)){
|
||||
$errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable);
|
||||
$errors = BlockTest::computeConsistencyCheckDiff($oldTablePath, $newTable, $newTiles);
|
||||
|
||||
if(count($errors) > 0){
|
||||
echo count($errors) . " changes detected:\n";
|
||||
@ -47,5 +47,6 @@ if(file_exists($oldTablePath)){
|
||||
}
|
||||
|
||||
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));
|
||||
|
Loading…
x
Reference in New Issue
Block a user