diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 8e1ba493e..ed2987473 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -46,7 +46,7 @@ jobs: run: echo ::set-output name=NAME::$(echo "${GITHUB_REPOSITORY,,}") - name: Build image for tag - uses: docker/build-push-action@v3.1.1 + uses: docker/build-push-action@v3.2.0 with: push: true context: ./pocketmine-mp @@ -59,7 +59,7 @@ jobs: - name: Build image for major tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v3.1.1 + uses: docker/build-push-action@v3.2.0 with: push: true context: ./pocketmine-mp @@ -72,7 +72,7 @@ jobs: - name: Build image for minor tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v3.1.1 + uses: docker/build-push-action@v3.2.0 with: push: true context: ./pocketmine-mp @@ -85,7 +85,7 @@ jobs: - name: Build image for latest tag if: steps.channel.outputs.CHANNEL == 'stable' - uses: docker/build-push-action@v3.1.1 + uses: docker/build-push-action@v3.2.0 with: push: true context: ./pocketmine-mp diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index 8adc0f4dd..a7a251c77 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.21.1 + uses: shivammathur/setup-php@2.21.2 with: php-version: 8.0 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index ceb3b1d9d..9c26c3ff1 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -18,7 +18,7 @@ jobs: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.21.1 + uses: shivammathur/setup-php@2.21.2 with: php-version: 8.0 @@ -69,7 +69,7 @@ jobs: ${{ github.workspace }}/build_info.json - name: Create draft release - uses: ncipollo/release-action@v1.10.0 + uses: ncipollo/release-action@v1.11.1 with: artifacts: ${{ github.workspace }}/PocketMine-MP.phar,${{ github.workspace }}/start.*,${{ github.workspace }}/build_info.json commit: ${{ github.sha }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e41e74cc8..44a672b73 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,11 +13,11 @@ jobs: strategy: matrix: image: [ubuntu-20.04] - php: [8.0.18] + php: [8.0.23, 8.1.10] steps: - name: Build and prepare PHP cache - uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440 + uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8 with: php-version: ${{ matrix.php }} install-path: "./bin" @@ -31,13 +31,13 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.18] + php: [8.0.23, 8.1.10] steps: - uses: actions/checkout@v3 - name: Setup PHP - uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440 + uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8 with: php-version: ${{ matrix.php }} install-path: "./bin" @@ -69,13 +69,13 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.18] + php: [8.0.23, 8.1.10] steps: - uses: actions/checkout@v3 - name: Setup PHP - uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440 + uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8 with: php-version: ${{ matrix.php }} install-path: "./bin" @@ -107,7 +107,7 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.18] + php: [8.0.23, 8.1.10] steps: - uses: actions/checkout@v3 @@ -115,7 +115,7 @@ jobs: submodules: true - name: Setup PHP - uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440 + uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8 with: php-version: ${{ matrix.php }} install-path: "./bin" @@ -147,13 +147,13 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.18] + php: [8.0.23, 8.1.10] steps: - uses: actions/checkout@v3 - name: Setup PHP - uses: pmmp/setup-php-action@aa636a4fe0c1c035fd9a3f05e360eadd86e06440 + uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8 with: php-version: ${{ matrix.php }} install-path: "./bin" @@ -195,10 +195,10 @@ jobs: - uses: actions/checkout@v3 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.21.1 + uses: shivammathur/setup-php@2.21.2 with: php-version: 8.0 - tools: php-cs-fixer:3.8 + tools: php-cs-fixer:3.11 - name: Run PHP-CS-Fixer run: php-cs-fixer fix --dry-run --diff --ansi diff --git a/.github/workflows/update-php-versions.php b/.github/workflows/update-php-versions.php index 0ba7382cf..7ff798566 100644 --- a/.github/workflows/update-php-versions.php +++ b/.github/workflows/update-php-versions.php @@ -22,7 +22,8 @@ declare(strict_types=1); const VERSIONS = [ - "8.0" + "8.0", + "8.1" ]; $workflowFile = file_get_contents(__DIR__ . '/main.yml'); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 745f89cba..84c5ec297 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,32 @@ Larger contributions like feature additions should be preceded by a [Change Prop ## Other things you'll need - [git](https://git-scm.com/) +## Choosing a target branch +PocketMine-MP has three primary branches of development. + +| Type of change | `stable` | `next-minor` | `next-major` | +|:---------------|:--------:|:------------:|:------------:| +| Bug fixes | ✔️ | ✔️ | ✔️ | +| Improvements to API docs | ✔️ | ✔️ | ✔️ | +| Cleaning up code | ❌ | ✔️ | ✔️ | +| Changing code formatting or style | ❌ | ✔️ | ✔️ | +| Addition of new core features | ❌ | 🟡 Only if non-disruptive | ✔️ | +| Changing core behaviour (e.g. making something use threads) | ❌ | ✔️ | ✔️ | +| Addition of new configuration options | ❌ | 🟡 Only if optional | ✔️ | +| Addition of new API classes, methods or constants | ❌ | ✔️ | ✔️ | +| Deprecating API classes, methods or constants | ❌ | ✔️ | ✔️ | +| Adding optional parameters to an API method | ❌ | ✔️ | ✔️ | +| Changing API behaviour | ❌ | 🟡 Only if backwards-compatible | ✔️ | +| Removal of API | ❌ | ❌ | ✔️ | +| Backwards-incompatible API change (e.g. renaming a method) | ❌ | ❌ | ✔️ | + +### Notes +- **Non-disruptive** means that usage should not be significantly altered by the change. + - Examples of **non-disruptive** changes include adding new commands, or gameplay features like blocks and items. + - Examples of **disruptive** changes include changing the way the server is run, world format changes (since those require downtime for the user to convert their world). +- **API** includes all public and protected classes, functions and constants (unless marked as `@internal`). + - Private members are not part of the API, **unless in a trait**. + ## Making a pull request The basic procedure to create a pull request is: 1. [Fork the repository on GitHub](https://github.com/pmmp/PocketMine-MP/fork). This gives you your own copy of the repository to make changes to. diff --git a/build/generate-known-translation-apis.php b/build/generate-known-translation-apis.php index e799735c0..af617c523 100644 --- a/build/generate-known-translation-apis.php +++ b/build/generate-known-translation-apis.php @@ -25,7 +25,7 @@ namespace pocketmine\build\generate_known_translation_apis; use pocketmine\lang\Translatable; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function array_map; use function count; use function dirname; diff --git a/build/php b/build/php index f292501a7..14ed8eaad 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit f292501a703352ab793b07b7861f3e1b3860ed86 +Subproject commit 14ed8eaadd921407c87be4964a8726b22427e80e diff --git a/changelogs/4.7.md b/changelogs/4.7.md index 950158cd3..bdbdf5249 100644 --- a/changelogs/4.7.md +++ b/changelogs/4.7.md @@ -29,3 +29,18 @@ Released 16th August 2022. - Fixed crash when processing player skins with invalid geometry data. - Fixed spectator players being able to pick blocks using mousewheel click. - Improved supporting requirements for sugarcane. + +# 4.7.3 +Released 22nd August 2022. + +## General +- Added complete translations for Spanish and Vietnamese. +- All continuous integration (static analysis, unit tests, integration tests) are now performed on PHP 8.1 as well as 8.0. +- InventoryTransaction now verifies that stack sizes of items after the transaction don't exceed the maximum stack size of the item type or the containing inventory. + +## Fixes +- Fixed Normal generator crash on PHP 8.1. +- Fixed a race condition during async worker shutdown that could lead to tasks executing in the wrong order. This (very rarely) led to a crash in `PopulationTask` due to its preceding `GeneratorRegisterTask` not being executed. +- Fixed `/give` accepting negative amounts or amounts larger than 32767 (vanilla max). +- Fixed placement conditions for vines (no longer able to be placed on the side of cacti). +- Fixed incorrect documentation of `SignText::__construct()`. diff --git a/changelogs/4.8.md b/changelogs/4.8.md new file mode 100644 index 000000000..44e9e6896 --- /dev/null +++ b/changelogs/4.8.md @@ -0,0 +1,23 @@ +**For Minecraft: Bedrock Edition 1.19.21** + +### Note about API versions +Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps. +Plugin developers should **only** update their required API to this version if you need the changes in this build. + +**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do. + +# 4.8.0 +Released 24th August 2022. + +## General +- Added support for Minecraft: Bedrock Edition 1.19.21. +- Removed support for older versions. + +# 4.8.1 +Released 26th August 2022. + +## General +- Crashdumps now include JIT mode information for use by the Crash Archive. + +## Fixes +- Fixed uninitialized offset error in `DyeColorIdMap` when given invalid dye color IDs. diff --git a/changelogs/4.9.md b/changelogs/4.9.md new file mode 100644 index 000000000..6bbd5af8e --- /dev/null +++ b/changelogs/4.9.md @@ -0,0 +1,36 @@ +**For Minecraft: Bedrock Edition 1.19.30** + +### Note about API versions +Plugins which don't touch the protocol and compatible with any previous 4.x.y version will also run on these releases and do not need API bumps. +Plugin developers should **only** update their required API to this version if you need the changes in this build. + +**WARNING: If your plugin uses the protocol, you're not shielded by API change constraints.** You should consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you do. + +# 4.9.0 +Released 20th September 2022. + +## General +- Added support for Minecraft: Bedrock Edition 1.19.30. +- Removed support for older versions. + +# 4.9.1 +Released 11th October 2022. + +## Documentation +- Added and improved documentation for many API methods in `Player` and `Block`. +- Added missing `@internal` tag for `TaskHandler->setNextRun()`, `TaskHandler->remove()` and `TaskHandler->run()`. + +## Fixes +- Flight state is now locked by the server in spectator mode. This prevents any attempt by the client to toggle flight mode. +- Fixed entity health exceeding its max health after the expiry of Health Boost effect. +- Fixed burp sound not being played when a player eats food. +- Fixed placement conditions for mushrooms - they can now only be placed when the light level at the target is <= 12, or on podzol or mycelium. +- Fixed sign text appearing to change colour and/or glow when using dye on a sign - since this feature is not yet implemented, no change should occur. +- Fixed players drowning when sprint-swimming on the surface of water. + +## Internals +- Added more detailed debug logging during the player login sequence. +- Silenced debug spam during `PreSpawnPacketHandler`, considerably reducing debug noise when players join. +- Fixed an edge case in `InventoryManager->removeWindow()`. This bug didn't have any effect on stable versions, but caused a `next-minor` development version to crash. +- `Item`s returned by event getters are now cloned if modifying the result will have no useful side effects. +- Updated `pocketmine/bedrock-data` to [`1.11.1`](https://github.com/pmmp/BedrockData/tree/1.11.1%2Bbedrock-1.19.30), which reduces bandwidth consumption during logins by not sending useless biome generation data. diff --git a/composer.json b/composer.json index 8d5d7adad..ba334459b 100644 --- a/composer.json +++ b/composer.json @@ -34,14 +34,14 @@ "adhocore/json-comment": "^1.1", "fgrosse/phpasn1": "^2.3", "netresearch/jsonmapper": "^4.0", - "pocketmine/bedrock-data": "~1.10.0+bedrock-1.19.20", - "pocketmine/bedrock-protocol": "~12.1.0+bedrock-1.19.20", + "pocketmine/bedrock-data": "~1.11.0+bedrock-1.19.30", + "pocketmine/bedrock-protocol": "~13.0.0+bedrock-1.19.30", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/classloader": "^0.2.0", "pocketmine/color": "^0.2.0", "pocketmine/errorhandler": "^0.6.0", - "pocketmine/locale-data": "~2.8.0", + "pocketmine/locale-data": "~2.8.0 <2.8.9", "pocketmine/log": "^0.4.0", "pocketmine/log-pthreads": "^0.4.0", "pocketmine/math": "^0.4.0", @@ -50,10 +50,11 @@ "pocketmine/raklib-ipc": "^0.1.0", "pocketmine/snooze": "^0.3.0", "ramsey/uuid": "^4.1", + "symfony/filesystem": "^5.4", "webmozart/path-util": "^2.3" }, "require-dev": { - "phpstan/phpstan": "1.8.2", + "phpstan/phpstan": "1.8.9", "phpstan/phpstan-phpunit": "^1.1.0", "phpstan/phpstan-strict-rules": "^1.2.0", "phpunit/phpunit": "^9.2" diff --git a/composer.lock b/composer.lock index 8eb97af45..ede3e8afe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "df65cc2917f6ab13745a48a6eb2fe48a", + "content-hash": "f652fc7867f7fd3d183df26e44658cd0", "packages": [ { "name": "adhocore/json-comment", - "version": "1.1.2", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/adhocore/php-json-comment.git", - "reference": "fc2f76979f0a44a5f5bc2a2b600d0762fe0e78e7" + "reference": "651023f9fe52e9efa2198cbaf6e481d1968e2377" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/adhocore/php-json-comment/zipball/fc2f76979f0a44a5f5bc2a2b600d0762fe0e78e7", - "reference": "fc2f76979f0a44a5f5bc2a2b600d0762fe0e78e7", + "url": "https://api.github.com/repos/adhocore/php-json-comment/zipball/651023f9fe52e9efa2198cbaf6e481d1968e2377", + "reference": "651023f9fe52e9efa2198cbaf6e481d1968e2377", "shasum": "" }, "require": { @@ -51,15 +51,19 @@ ], "support": { "issues": "https://github.com/adhocore/php-json-comment/issues", - "source": "https://github.com/adhocore/php-json-comment/tree/1.1.2" + "source": "https://github.com/adhocore/php-json-comment/tree/1.2.1" }, "funding": [ { "url": "https://paypal.me/ji10", "type": "custom" + }, + { + "url": "https://github.com/adhocore", + "type": "github" } ], - "time": "2021-04-09T03:06:06+00:00" + "time": "2022-10-02T11:22:07+00:00" }, { "name": "brick/math", @@ -245,16 +249,16 @@ }, { "name": "pocketmine/bedrock-data", - "version": "1.10.0+bedrock-1.19.20", + "version": "1.11.1+bedrock-1.19.30", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "43610f6749f22d15ede6b60ed5402bdeff47453e" + "reference": "9ec9a9645ba19f04dd4e39d6d9bd30b562dfe90c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/43610f6749f22d15ede6b60ed5402bdeff47453e", - "reference": "43610f6749f22d15ede6b60ed5402bdeff47453e", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/9ec9a9645ba19f04dd4e39d6d9bd30b562dfe90c", + "reference": "9ec9a9645ba19f04dd4e39d6d9bd30b562dfe90c", "shasum": "" }, "type": "library", @@ -265,22 +269,22 @@ "description": "Blobs of data generated from Minecraft: Bedrock Edition, used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/BedrockData/issues", - "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.19.20" + "source": "https://github.com/pmmp/BedrockData/tree/1.11.1+bedrock-1.19.30" }, - "time": "2022-08-09T17:44:22+00:00" + "time": "2022-09-27T22:00:01+00:00" }, { "name": "pocketmine/bedrock-protocol", - "version": "12.1.0+bedrock-1.19.20", + "version": "13.0.0+bedrock-1.19.30", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "f754df1c7becfad89599052e3ac07852a02dc4dd" + "reference": "94de2221676ca717587e1ff4e45445c24ada1749" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/f754df1c7becfad89599052e3ac07852a02dc4dd", - "reference": "f754df1c7becfad89599052e3ac07852a02dc4dd", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/94de2221676ca717587e1ff4e45445c24ada1749", + "reference": "94de2221676ca717587e1ff4e45445c24ada1749", "shasum": "" }, "require": { @@ -312,9 +316,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/12.1.0+bedrock-1.19.20" + "source": "https://github.com/pmmp/BedrockProtocol/tree/bedrock-1.19.30" }, - "time": "2022-08-16T19:55:35+00:00" + "time": "2022-09-20T18:35:00+00:00" }, { "name": "pocketmine/binaryutils", @@ -532,16 +536,16 @@ }, { "name": "pocketmine/locale-data", - "version": "2.8.4", + "version": "2.8.7", "source": { "type": "git", "url": "https://github.com/pmmp/Language.git", - "reference": "6709467487d270c962deee16972c7786949d1511" + "reference": "e115d3d64a508065f1cedad1be55528906308456" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Language/zipball/6709467487d270c962deee16972c7786949d1511", - "reference": "6709467487d270c962deee16972c7786949d1511", + "url": "https://api.github.com/repos/pmmp/Language/zipball/e115d3d64a508065f1cedad1be55528906308456", + "reference": "e115d3d64a508065f1cedad1be55528906308456", "shasum": "" }, "type": "library", @@ -549,9 +553,9 @@ "description": "Language resources used by PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Language/issues", - "source": "https://github.com/pmmp/Language/tree/2.8.4" + "source": "https://github.com/pmmp/Language/tree/2.8.7" }, - "time": "2022-08-16T17:47:52+00:00" + "time": "2022-08-21T20:37:16+00:00" }, { "name": "pocketmine/log", @@ -640,16 +644,16 @@ }, { "name": "pocketmine/math", - "version": "0.4.2", + "version": "0.4.3", "source": { "type": "git", "url": "https://github.com/pmmp/Math.git", - "reference": "aacc3759a508a69dfa5bc4dfa770ab733c5c94bf" + "reference": "47a243d320b01c8099d65309967934c188111549" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/Math/zipball/aacc3759a508a69dfa5bc4dfa770ab733c5c94bf", - "reference": "aacc3759a508a69dfa5bc4dfa770ab733c5c94bf", + "url": "https://api.github.com/repos/pmmp/Math/zipball/47a243d320b01c8099d65309967934c188111549", + "reference": "47a243d320b01c8099d65309967934c188111549", "shasum": "" }, "require": { @@ -658,7 +662,7 @@ }, "require-dev": { "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "1.2.0", + "phpstan/phpstan": "1.8.2", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^8.5 || ^9.5" }, @@ -675,9 +679,9 @@ "description": "PHP library containing math related code used in PocketMine-MP", "support": { "issues": "https://github.com/pmmp/Math/issues", - "source": "https://github.com/pmmp/Math/tree/0.4.2" + "source": "https://github.com/pmmp/Math/tree/0.4.3" }, - "time": "2021-12-05T01:15:17+00:00" + "time": "2022-08-25T18:43:37+00:00" }, { "name": "pocketmine/nbt", @@ -723,16 +727,16 @@ }, { "name": "pocketmine/raklib", - "version": "0.14.4", + "version": "0.14.5", "source": { "type": "git", "url": "https://github.com/pmmp/RakLib.git", - "reference": "1ea8e3b95a1b6bf785dc27d76578657be4185f42" + "reference": "85b4e5cb7117d37e010eeadb3ff53b21276c6f48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/RakLib/zipball/1ea8e3b95a1b6bf785dc27d76578657be4185f42", - "reference": "1ea8e3b95a1b6bf785dc27d76578657be4185f42", + "url": "https://api.github.com/repos/pmmp/RakLib/zipball/85b4e5cb7117d37e010eeadb3ff53b21276c6f48", + "reference": "85b4e5cb7117d37e010eeadb3ff53b21276c6f48", "shasum": "" }, "require": { @@ -744,7 +748,7 @@ "pocketmine/log": "^0.3.0 || ^0.4.0" }, "require-dev": { - "phpstan/phpstan": "1.5.4", + "phpstan/phpstan": "1.7.7", "phpstan/phpstan-strict-rules": "^1.0" }, "type": "library", @@ -760,9 +764,9 @@ "description": "A RakNet server implementation written in PHP", "support": { "issues": "https://github.com/pmmp/RakLib/issues", - "source": "https://github.com/pmmp/RakLib/tree/0.14.4" + "source": "https://github.com/pmmp/RakLib/tree/0.14.5" }, - "time": "2022-04-17T18:42:17+00:00" + "time": "2022-08-25T16:16:44+00:00" }, { "name": "pocketmine/raklib-ipc", @@ -926,20 +930,20 @@ }, { "name": "ramsey/uuid", - "version": "4.4.0", + "version": "4.5.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "373f7bacfcf3de038778ff27dcce5672ddbf4c8a" + "reference": "a161a26d917604dc6d3aa25100fddf2556e9f35d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/373f7bacfcf3de038778ff27dcce5672ddbf4c8a", - "reference": "373f7bacfcf3de038778ff27dcce5672ddbf4c8a", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/a161a26d917604dc6d3aa25100fddf2556e9f35d", + "reference": "a161a26d917604dc6d3aa25100fddf2556e9f35d", "shasum": "" }, "require": { - "brick/math": "^0.8 || ^0.9 || ^0.10", + "brick/math": "^0.8.8 || ^0.9 || ^0.10", "ext-ctype": "*", "ext-json": "*", "php": "^8.0", @@ -960,12 +964,13 @@ "php-mock/php-mock-mockery": "^1.3", "php-parallel-lint/php-parallel-lint": "^1.1", "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-mockery": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", "phpunit/phpunit": "^8.5 || ^9", - "slevomat/coding-standard": "^7.0", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", "squizlabs/php_codesniffer": "^3.5", "vimeo/psalm": "^4.9" }, @@ -1003,7 +1008,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.4.0" + "source": "https://github.com/ramsey/uuid/tree/4.5.1" }, "funding": [ { @@ -1015,7 +1020,319 @@ "type": "tidelift" } ], - "time": "2022-08-05T17:58:37+00:00" + "time": "2022-09-16T03:22:46+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.13", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "ac09569844a9109a5966b9438fc29113ce77cf51" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51", + "reference": "ac09569844a9109a5966b9438fc29113ce77cf51", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.13" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-09-21T19:53:16+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T07:21:04+00:00" }, { "name": "symfony/polyfill-php81", @@ -1338,16 +1655,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.14.0", + "version": "v4.15.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", "shasum": "" }, "require": { @@ -1388,9 +1705,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" }, - "time": "2022-05-31T20:59:12+00:00" + "time": "2022-09-04T07:30:47+00:00" }, { "name": "phar-io/manifest", @@ -1503,245 +1820,18 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" - }, - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" - }, - "time": "2022-03-15T21:29:03+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.15.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" - }, - "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" - }, - "time": "2021-12-08T12:19:24+00:00" - }, { "name": "phpstan/phpstan", - "version": "1.8.2", + "version": "1.8.9", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "c53312ecc575caf07b0e90dee43883fdf90ca67c" + "reference": "3a72d9d9f2528fbd50c2d8fcf155fd9f74ade3f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c53312ecc575caf07b0e90dee43883fdf90ca67c", - "reference": "c53312ecc575caf07b0e90dee43883fdf90ca67c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/3a72d9d9f2528fbd50c2d8fcf155fd9f74ade3f2", + "reference": "3a72d9d9f2528fbd50c2d8fcf155fd9f74ade3f2", "shasum": "" }, "require": { @@ -1765,9 +1855,13 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.2" + "source": "https://github.com/phpstan/phpstan/tree/1.8.9" }, "funding": [ { @@ -1778,16 +1872,12 @@ "url": "https://github.com/phpstan", "type": "github" }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, { "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", "type": "tidelift" } ], - "time": "2022-07-20T09:57:31+00:00" + "time": "2022-10-13T13:40:18+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -1843,21 +1933,21 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.3.0", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "543675a9be82d4befb9ca0bd8cdc9d211665037f" + "reference": "23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/543675a9be82d4befb9ca0bd8cdc9d211665037f", - "reference": "543675a9be82d4befb9ca0bd8cdc9d211665037f", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6", + "reference": "23e5f377ee6395a1a04842d3d6ed4bd25e7b44a6", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.7.15" + "phpstan/phpstan": "^1.8.6" }, "require-dev": { "nikic/php-parser": "^4.13.0", @@ -1885,29 +1975,29 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.3.0" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.4.4" }, - "time": "2022-06-24T06:47:20+00:00" + "time": "2022-09-21T11:38:17+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.15", + "version": "9.2.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", + "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.13.0", + "nikic/php-parser": "^4.14", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -1956,7 +2046,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" }, "funding": [ { @@ -1964,7 +2054,7 @@ "type": "github" } ], - "time": "2022-03-07T09:28:20+00:00" + "time": "2022-08-30T12:24:04+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2209,16 +2299,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.21", + "version": "9.5.25", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1" + "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1", - "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", "shasum": "" }, "require": { @@ -2233,7 +2323,6 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", @@ -2241,19 +2330,16 @@ "phpunit/php-timer": "^5.0.2", "sebastian/cli-parser": "^1.0.1", "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", + "sebastian/comparator": "^4.0.8", "sebastian/diff": "^4.0.3", "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", + "sebastian/exporter": "^4.0.5", "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.0", + "sebastian/type": "^3.2", "sebastian/version": "^3.0.2" }, - "require-dev": { - "phpspec/prophecy-phpunit": "^2.0.1" - }, "suggest": { "ext-soap": "*", "ext-xdebug": "*" @@ -2295,7 +2381,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" }, "funding": [ { @@ -2305,9 +2391,13 @@ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "time": "2022-06-19T12:14:25+00:00" + "time": "2022-09-25T03:44:45+00:00" }, { "name": "sebastian/cli-parser", @@ -2478,16 +2568,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -2540,7 +2630,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -2548,7 +2638,7 @@ "type": "github" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", @@ -2738,16 +2828,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", "shasum": "" }, "require": { @@ -2803,7 +2893,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" }, "funding": [ { @@ -2811,7 +2901,7 @@ "type": "github" } ], - "time": "2021-11-11T14:18:36+00:00" + "time": "2022-09-14T06:03:37+00:00" }, { "name": "sebastian/global-state", @@ -3166,16 +3256,16 @@ }, { "name": "sebastian/type", - "version": "3.0.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", "shasum": "" }, "require": { @@ -3187,7 +3277,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -3210,7 +3300,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" }, "funding": [ { @@ -3218,7 +3308,7 @@ "type": "github" } ], - "time": "2022-03-15T09:54:48+00:00" + "time": "2022-09-12T14:47:03+00:00" }, { "name": "sebastian/version", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9ebc98502..6fc15b07a 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,4 +1,5 @@ includes: + - tests/phpstan/analyse-for-current-php-version.neon.php - tests/phpstan/configs/actual-problems.neon - tests/phpstan/configs/gc-hacks.neon - tests/phpstan/configs/impossible-generics.neon diff --git a/resources/pocketmine.yml b/resources/pocketmine.yml index f922cb225..4ad8ea9c0 100644 --- a/resources/pocketmine.yml +++ b/resources/pocketmine.yml @@ -168,6 +168,9 @@ timings: host: timings.pmmp.io console: + #Whether to accept commands via the console. If disabled, anything typed on the console will be ignored. + #Useful to save resources on headless servers where the console is never used (e.g. hosted server, Docker, etc.) + enable-input: true #Choose whether to enable server stats reporting on the console title. #NOTE: The title ticker will be disabled regardless if console colours are not enabled. title-tick: true diff --git a/resources/resource_packs.yml b/resources/resource_packs.yml index 39677852d..f236117d5 100644 --- a/resources/resource_packs.yml +++ b/resources/resource_packs.yml @@ -10,3 +10,4 @@ resource_stack: # - natural.zip # - vanilla.zip #If you want to force clients to use vanilla resources, you must place a vanilla resource pack in your resources folder and add it to the stack here. + #To specify a resource encryption key, put the key in the .key file alongside the resource pack. Example: vanilla.zip.key diff --git a/src/MemoryManager.php b/src/MemoryManager.php index 49a728c89..cf7bc745d 100644 --- a/src/MemoryManager.php +++ b/src/MemoryManager.php @@ -30,7 +30,7 @@ use pocketmine\scheduler\GarbageCollectionTask; use pocketmine\timings\Timings; use pocketmine\utils\Process; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function arsort; use function count; use function fclose; diff --git a/src/PocketMine.php b/src/PocketMine.php index 9cb8eedf8..faccb27e1 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -34,9 +34,10 @@ namespace pocketmine { use pocketmine\utils\Timezone; use pocketmine\utils\Utils; use pocketmine\wizard\SetupWizard; - use Webmozart\PathUtil\Path; + use Symfony\Component\Filesystem\Path; use function defined; use function extension_loaded; + use function function_exists; use function getcwd; use function phpversion; use function preg_match; @@ -160,7 +161,7 @@ namespace pocketmine { if(PHP_DEBUG !== 0){ $logger->warning("This PHP binary was compiled in debug mode. This has a major impact on performance."); } - if(extension_loaded("xdebug")){ + if(extension_loaded("xdebug") && (!function_exists('xdebug_info') || count(xdebug_info('mode')) !== 0)){ $logger->warning("Xdebug extension is enabled. This has a major impact on performance."); } if(((int) ini_get('zend.assertions')) !== -1){ @@ -176,10 +177,10 @@ namespace pocketmine { --------------------------------------- ! WARNING ! --------------------------------------- - You're using PHP 8.0 with JIT enabled. This provides significant performance improvements. + You're using PHP with JIT enabled. This provides significant performance improvements. HOWEVER, it is EXPERIMENTAL, and has already been seen to cause weird and unexpected bugs. Proceed with caution. - If you want to report any bugs, make sure to mention that you are using PHP 8.0 with JIT. + If you want to report any bugs, make sure to mention that you have enabled PHP JIT. To turn off JIT, change `opcache.jit` to `0` in your php.ini file. ------------------------------------------------------------------------------------------- diff --git a/src/Server.php b/src/Server.php index 3128a7c39..147e5a00b 100644 --- a/src/Server.php +++ b/src/Server.php @@ -31,7 +31,7 @@ use pocketmine\command\Command; use pocketmine\command\CommandSender; use pocketmine\command\SimpleCommandMap; use pocketmine\console\ConsoleCommandSender; -use pocketmine\console\ConsoleReaderThread; +use pocketmine\console\ConsoleReaderChildProcessDaemon; use pocketmine\crafting\CraftingManager; use pocketmine\crafting\CraftingManagerFromDataHelper; use pocketmine\crash\CrashDump; @@ -88,12 +88,12 @@ use pocketmine\promise\PromiseResolver; use pocketmine\resourcepacks\ResourcePackManager; use pocketmine\scheduler\AsyncPool; use pocketmine\snooze\SleeperHandler; -use pocketmine\snooze\SleeperNotifier; use pocketmine\stats\SendUsageTask; use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; use pocketmine\updater\UpdateChecker; use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\BroadcastLoggerForwarder; use pocketmine\utils\Config; use pocketmine\utils\Filesystem; use pocketmine\utils\Internet; @@ -115,7 +115,7 @@ use pocketmine\world\World; use pocketmine\world\WorldCreationOptions; use pocketmine\world\WorldManager; use Ramsey\Uuid\UuidInterface; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function array_sum; use function base64_encode; use function cli_set_process_title; @@ -132,6 +132,7 @@ use function get_class; use function ini_set; use function is_array; use function is_dir; +use function is_int; use function is_object; use function is_resource; use function is_string; @@ -165,7 +166,6 @@ use function zlib_encode; use const DIRECTORY_SEPARATOR; use const PHP_EOL; use const PHP_INT_MAX; -use const PTHREADS_INHERIT_NONE; use const ZLIB_ENCODING_GZIP; /** @@ -225,7 +225,8 @@ class Server{ private MemoryManager $memoryManager; - private ConsoleReaderThread $console; + private ?ConsoleReaderChildProcessDaemon $console = null; + private ?ConsoleCommandSender $consoleSender = null; private SimpleCommandMap $commandMap; @@ -570,6 +571,7 @@ class Server{ $playerPos = null; $spawn = $world->getSpawnLocation(); } + /** @phpstan-var PromiseResolver $playerPromiseResolver */ $playerPromiseResolver = new PromiseResolver(); $world->requestChunkPopulation($spawn->getFloorX() >> Chunk::COORD_BIT_SIZE, $spawn->getFloorZ() >> Chunk::COORD_BIT_SIZE, null)->onCompletion( function() use ($playerPromiseResolver, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{ @@ -607,6 +609,10 @@ class Server{ } /** + * @deprecated This method's results are unpredictable. The string "Steve" will return the player named "SteveJobs", + * until another player named "SteveJ" joins the server, at which point it will return that player instead. Prefer + * filtering the results of {@link Server::getOnlinePlayers()} yourself. + * * Returns an online player whose name begins with or equals the given string (case insensitive). * The closest match will be returned, or null if there are no online matches. * @@ -1043,22 +1049,14 @@ class Server{ $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET))); $this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3))))); - //TODO: move console parts to a separate component - $consoleSender = new ConsoleCommandSender($this, $this->language); - $this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $consoleSender); - $this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $consoleSender); + $forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language); + $this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_ADMINISTRATIVE, $forwarder); + $this->subscribeToBroadcastChannel(self::BROADCAST_CHANNEL_USERS, $forwarder); - $consoleNotifier = new SleeperNotifier(); - $commandBuffer = new \Threaded(); - $this->console = new ConsoleReaderThread($commandBuffer, $consoleNotifier); - $this->tickSleeper->addNotifier($consoleNotifier, function() use ($commandBuffer, $consoleSender) : void{ - Timings::$serverCommand->startTiming(); - while(($line = $commandBuffer->shift()) !== null){ - $this->dispatchCommand($consoleSender, (string) $line); - } - Timings::$serverCommand->stopTiming(); - }); - $this->console->start(PTHREADS_INHERIT_NONE); + //TODO: move console parts to a separate component + if($this->configGroup->getPropertyBool("console.enable-input", true)){ + $this->console = new ConsoleReaderChildProcessDaemon($this->logger); + } $this->tickProcessor(); $this->forceShutdown(); @@ -1511,7 +1509,7 @@ class Server{ $this->configGroup->save(); } - if(isset($this->console)){ + if($this->console !== null){ $this->getLogger()->debug("Closing console"); $this->console->quit(); } @@ -1650,12 +1648,14 @@ class Server{ ], 10, [], $postUrlError); if($reply !== null && is_object($data = json_decode($reply->getBody()))){ - if(isset($data->crashId) && isset($data->crashUrl)){ + if(isset($data->crashId) && is_int($data->crashId) && isset($data->crashUrl) && is_string($data->crashUrl)){ $reportId = $data->crashId; $reportUrl = $data->crashUrl; $this->logger->emergency($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_crash_archive($reportUrl, (string) $reportId))); - }elseif(isset($data->error)){ + }elseif(isset($data->error) && is_string($data->error)){ $this->logger->emergency("Automatic crash report submission failed: $data->error"); + }else{ + $this->logger->emergency("Invalid JSON response received from crash archive: " . $reply->getBody()); } }else{ $this->logger->emergency("Failed to communicate with crash archive: $postUrlError"); @@ -1716,7 +1716,7 @@ class Server{ $session = $player->getNetworkSession(); $position = $player->getPosition(); $this->logger->info($this->language->translate(KnownTranslationFactory::pocketmine_player_logIn( - TextFormat::AQUA . $player->getName() . TextFormat::WHITE, + TextFormat::AQUA . $player->getName() . TextFormat::RESET, $session->getIp(), (string) $session->getPort(), (string) $player->getId(), @@ -1853,6 +1853,15 @@ class Server{ $this->getMemoryManager()->check(); + if($this->console !== null){ + Timings::$serverCommand->startTiming(); + while(($line = $this->console->readLine()) !== null){ + $this->consoleSender ??= new ConsoleCommandSender($this, $this->language); + $this->dispatchCommand($this->consoleSender, $line); + } + Timings::$serverCommand->stopTiming(); + } + Timings::$serverTick->stopTiming(); $now = microtime(true); diff --git a/src/VersionInfo.php b/src/VersionInfo.php index d8e7cdd47..36d399fc8 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,7 +31,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "4.7.3"; + public const BASE_VERSION = "4.9.2"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "beta"; diff --git a/src/block/Block.php b/src/block/Block.php index b68080720..6f2f1b27e 100644 --- a/src/block/Block.php +++ b/src/block/Block.php @@ -78,25 +78,47 @@ class Block{ $this->position = clone $this->position; } + /** + * Returns an object containing information about how to identify and store this block type, such as its legacy + * numeric ID(s), tile type (if any), and legacy variant metadata. + */ public function getIdInfo() : BlockIdentifier{ return $this->idInfo; } + /** + * Returns the printable English name of the block. + */ public function getName() : string{ return $this->fallbackName; } + /** + * @deprecated + * + * Returns the legacy numeric Minecraft block ID. + */ public function getId() : int{ return $this->idInfo->getBlockId(); } /** * @internal + * + * Returns the full blockstate ID of this block. This is a compact way of representing a blockstate used to store + * blocks in chunks at runtime. + * + * This ID can be used to later obtain a copy of this block using {@link BlockFactory::get()}. */ public function getFullId() : int{ return ($this->getId() << self::INTERNAL_METADATA_BITS) | $this->getMeta(); } + /** + * Returns the block as an item. + * State information such as facing, powered/unpowered, open/closed, etc., is discarded. + * Type information such as colour, wood type, etc. is preserved. + */ public function asItem() : Item{ return ItemFactory::getInstance()->get( $this->idInfo->getItemId(), @@ -104,6 +126,12 @@ class Block{ ); } + /** + * @deprecated + * + * Returns the legacy Minecraft block meta value. This is a mixed-purpose value, which is used to store different + * things for different blocks. + */ public function getMeta() : int{ $stateMeta = $this->writeStateToMeta(); assert(($stateMeta & ~$this->getStateBitmask()) === 0); @@ -116,6 +144,7 @@ class Block{ /** * Returns a bitmask used to extract state bits from block metadata. + * This is used to remove unwanted information from the legacy meta value when getting the block as an item. */ public function getStateBitmask() : int{ return 0; @@ -143,6 +172,12 @@ class Block{ $this->collisionBoxes = null; } + /** + * Writes information about the block into the world. This writes the blockstate ID into the chunk, and creates + * and/or removes tiles as necessary. + * + * Note: Do not call this directly. Pass the block to {@link World::setBlock()} instead. + */ public function writeStateToWorld() : void{ $world = $this->position->getWorld(); $world->getOrLoadChunkAtPosition($this->position)->setFullBlock($this->position->x & Chunk::COORD_MASK, $this->position->y, $this->position->z & Chunk::COORD_MASK, $this->getFullId()); @@ -168,7 +203,7 @@ class Block{ } /** - * Returns a type ID that identifies this type of block. This does not include information like facing, colour, + * Returns a type ID that identifies this type of block. This does not include information like facing, open/closed, * powered/unpowered, etc. */ public function getTypeId() : int{ @@ -199,22 +234,36 @@ class Block{ return true; } + /** + * Returns whether this block can be replaced by another block placed in the same position. + */ public function canBeReplaced() : bool{ return false; } + /** + * Returns whether this block can replace the given block in the given placement conditions. + * This is used to allow slabs of the same type to combine into double slabs. + */ public function canBePlacedAt(Block $blockReplace, Vector3 $clickVector, int $face, bool $isClickedBlock) : bool{ return $blockReplace->canBeReplaced(); } /** - * Places the Block, using block space and block target, and side. Returns if the block has been placed. + * Generates a block transaction to set all blocks affected by placing this block. Usually this is just the block + * itself, but may be multiple blocks in some cases (such as doors). + * + * @return bool whether the placement should go ahead */ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ $tx->addBlock($blockReplace->position, $this); return true; } + /** + * Called immediately after the block has been placed in the world. Since placement uses a block transaction, some + * things may not be possible until after the transaction has been executed. + */ public function onPostPlace() : void{ } @@ -254,7 +303,7 @@ class Block{ /** * Called when this block is randomly updated due to chunk ticking. - * WARNING: This will not be called if ticksRandomly() does not return true! + * WARNING: This will not be called if {@link Block::ticksRandomly()} does not return true! */ public function onRandomTick() : void{ @@ -275,8 +324,7 @@ class Block{ } /** - * Called when this block is attacked (left-clicked). This is called when a player left-clicks the block to try and - * start to break it in survival mode. + * Called when this block is attacked (left-clicked) by a player attempting to start breaking it in survival. * * @return bool if an action took place, prevents starting to break the block if true. */ @@ -284,11 +332,19 @@ class Block{ return false; } + /** + * Returns a multiplier applied to the velocity of entities moving on top of this block. A higher value will make + * the block more slippery (like ice). + * + * @return float 0.0-1.0 + */ public function getFrictionFactor() : float{ return 0.6; } /** + * Returns the amount of light emitted by this block. + * * @return int 0-15 */ public function getLightLevel() : int{ @@ -331,10 +387,6 @@ class Block{ return false; } - public function hasEntityCollision() : bool{ - return false; - } - /** * Returns whether entities can climb up this block. */ @@ -342,10 +394,6 @@ class Block{ return false; } - public function addVelocityToEntity(Entity $entity) : ?Vector3{ - return null; - } - final public function getPosition() : Position{ return $this->position; } @@ -428,6 +476,7 @@ class Block{ /** * Returns the item that players will equip when middle-clicking on this block. + * If addUserData is true, additional data may be added, such as banner patterns, chest contents, etc. */ public function getPickedItem(bool $addUserData = false) : Item{ $item = $this->asItem(); @@ -551,7 +600,7 @@ class Block{ } /** - * Checks for collision against an AxisAlignedBB + * Returns whether any of the block's collision boxes intersect with the given AxisAlignedBB. */ public function collidesWithBB(AxisAlignedBB $bb) : bool{ foreach($this->getCollisionBoxes() as $bb2){ @@ -563,10 +612,21 @@ class Block{ return false; } + /** + * Returns whether the block has actions to be executed when an entity enters its cell (full cube space). + * + * @see Block::onEntityInside() + */ + public function hasEntityCollision() : bool{ + return false; + } + /** * Called when an entity's bounding box clips inside this block's cell. Note that the entity may not be intersecting * with the collision box or bounding box. * + * WARNING: This will not be called if {@link Block::hasEntityCollision()} returns false. + * * @return bool Whether the block is still the same after the intersection. If it changed (e.g. due to an explosive * being ignited), this should return false. */ @@ -574,6 +634,19 @@ class Block{ return true; } + /** + * Returns a direction vector describing which way an entity intersecting this block should be pushed. + * This is used by liquids to push entities in liquid currents. + * + * The returned vector is summed with vectors from every other block the entity is intersecting, and normalized to + * produce a final direction vector. + * + * WARNING: This will not be called if {@link Block::hasEntityCollision()} does not return true! + */ + public function addVelocityToEntity(Entity $entity) : ?Vector3{ + return null; + } + /** * Called when an entity lands on this block (usually due to falling). * @return float|null The new vertical velocity of the entity, or null if unchanged. @@ -583,6 +656,13 @@ class Block{ } /** + * Returns an array of collision bounding boxes for this block. + * These are used for: + * - entity movement collision checks (to ensure entities can't clip through blocks) + * - projectile flight paths + * - block placement (to ensure the player can't place blocks inside itself or another entity) + * - anti-cheat checks in plugins + * * @return AxisAlignedBB[] */ final public function getCollisionBoxes() : array{ @@ -613,6 +693,10 @@ class Block{ return [AxisAlignedBB::one()]; } + /** + * Returns the type of support that the block can provide on the given face. This is used to determine whether + * blocks placed on the given face can be supported by this block. + */ public function getSupportType(int $facing) : SupportType{ return SupportType::FULL(); } @@ -623,6 +707,10 @@ class Block{ return count($bb) === 1 && $bb[0]->getAverageEdgeLength() >= 1 && $bb[0]->isCube(); } + /** + * Performs a ray trace along the line between the two positions using the block's collision boxes. + * Returns the intersection point closest to pos1, or null if no intersection occurred. + */ public function calculateIntercept(Vector3 $pos1, Vector3 $pos2) : ?RayTraceResult{ $bbs = $this->getCollisionBoxes(); if(count($bbs) === 0){ diff --git a/src/block/BlockFactory.php b/src/block/BlockFactory.php index 5f4676584..8aabeb9ca 100644 --- a/src/block/BlockFactory.php +++ b/src/block/BlockFactory.php @@ -523,12 +523,6 @@ class BlockFactory{ $this->registerAllMeta(...$leaves); $this->registerAllMeta(...$allSidedLogs); - static $sandstoneTypes = [ - Meta::SANDSTONE_NORMAL => "", - Meta::SANDSTONE_CHISELED => "Chiseled ", - Meta::SANDSTONE_CUT => "Cut ", - Meta::SANDSTONE_SMOOTH => "Smooth " - ]; $sandstoneBreakInfo = new BreakInfo(0.8, ToolType::PICKAXE, ToolTier::WOOD()->getHarvestLevel()); $this->registerAllMeta(new Stair(new BID(Ids::RED_SANDSTONE_STAIRS, 0), "Red Sandstone Stairs", $sandstoneBreakInfo)); $this->registerAllMeta(new Stair(new BID(Ids::SMOOTH_RED_SANDSTONE_STAIRS, 0), "Smooth Red Sandstone Stairs", $sandstoneBreakInfo)); @@ -536,7 +530,12 @@ class BlockFactory{ $this->registerAllMeta(new Stair(new BID(Ids::SMOOTH_SANDSTONE_STAIRS, 0), "Smooth Sandstone Stairs", $sandstoneBreakInfo)); $sandstones = []; $redSandstones = []; - foreach($sandstoneTypes as $variant => $prefix){ + foreach([ + Meta::SANDSTONE_NORMAL => "", + Meta::SANDSTONE_CHISELED => "Chiseled ", + Meta::SANDSTONE_CUT => "Cut ", + Meta::SANDSTONE_SMOOTH => "Smooth " + ] as $variant => $prefix){ $sandstones[] = new Opaque(new BID(Ids::SANDSTONE, $variant), $prefix . "Sandstone", $sandstoneBreakInfo); $redSandstones[] = new Opaque(new BID(Ids::RED_SANDSTONE, $variant), $prefix . "Red Sandstone", $sandstoneBreakInfo); } diff --git a/src/block/Cactus.php b/src/block/Cactus.php index d74e69c08..15e5fdc99 100644 --- a/src/block/Cactus.php +++ b/src/block/Cactus.php @@ -72,7 +72,7 @@ class Cactus extends Transparent{ * @return AxisAlignedBB[] */ protected function recalculateCollisionBoxes() : array{ - static $shrinkSize = 1 / 16; + $shrinkSize = 1 / 16; return [AxisAlignedBB::one()->contract($shrinkSize, 0, $shrinkSize)->trim(Facing::UP, $shrinkSize)]; } diff --git a/src/block/DeadBush.php b/src/block/DeadBush.php index 09754b07d..72cbc0de5 100644 --- a/src/block/DeadBush.php +++ b/src/block/DeadBush.php @@ -34,7 +34,7 @@ use function mt_rand; class DeadBush extends Flowable{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(!$this->getSide(Facing::DOWN)->isTransparent()){ + if($this->canBeSupportedBy($this->getSide(Facing::DOWN))){ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } @@ -42,7 +42,7 @@ class DeadBush extends Flowable{ } public function onNearbyBlockChange() : void{ - if($this->getSide(Facing::DOWN)->isTransparent()){ + if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN))){ $this->position->getWorld()->useBreakOn($this->position); } } @@ -64,4 +64,14 @@ class DeadBush extends Flowable{ public function getFlammability() : int{ return 100; } + + private function canBeSupportedBy(Block $block) : bool{ + $blockId = $block->getId(); + return $blockId === BlockLegacyIds::SAND + || $blockId === BlockLegacyIds::PODZOL + || $blockId === BlockLegacyIds::MYCELIUM + || $blockId === BlockLegacyIds::DIRT + || $blockId === BlockLegacyIds::HARDENED_CLAY + || $blockId === BlockLegacyIds::STAINED_HARDENED_CLAY; + } } diff --git a/src/block/Flowable.php b/src/block/Flowable.php index c3d6f3eb5..2b4e8a02b 100644 --- a/src/block/Flowable.php +++ b/src/block/Flowable.php @@ -26,6 +26,10 @@ namespace pocketmine\block; use pocketmine\block\utils\SupportType; use pocketmine\math\AxisAlignedBB; +/** + * "Flowable" blocks are destroyed if water flows into the same space as the block. These blocks usually don't have any + * collision boxes, and can't provide support for other blocks. + */ abstract class Flowable extends Transparent{ public function canBeFlowedInto() : bool{ diff --git a/src/block/Lever.php b/src/block/Lever.php index 07fbac45e..beec393b7 100644 --- a/src/block/Lever.php +++ b/src/block/Lever.php @@ -69,7 +69,6 @@ class Lever extends Flowable{ 5 => LeverFacing::UP_AXIS_Z(), 6 => LeverFacing::UP_AXIS_X(), 7 => LeverFacing::DOWN_AXIS_Z(), - default => throw new AssumptionFailedError("0x07 mask should make this impossible"), //phpstan doesn't understand :( }; $this->activated = ($stateMeta & BlockLegacyMetadata::LEVER_FLAG_POWERED) !== 0; diff --git a/src/block/Melon.php b/src/block/Melon.php index 6f4e62571..42d54c0ab 100644 --- a/src/block/Melon.php +++ b/src/block/Melon.php @@ -27,7 +27,7 @@ use pocketmine\item\Item; use pocketmine\item\VanillaItems; use function mt_rand; -class Melon extends Transparent{ +class Melon extends Opaque{ public function getDropsForCompatibleTool(Item $item) : array{ return [ diff --git a/src/block/Opaque.php b/src/block/Opaque.php index 43e4e95d6..c699bc258 100644 --- a/src/block/Opaque.php +++ b/src/block/Opaque.php @@ -23,6 +23,10 @@ declare(strict_types=1); namespace pocketmine\block; +/** + * Opaque blocks do not allow light to pass through. They are usually collidable full-cube blocks. + * Most blocks in Minecraft fall into this category. + */ class Opaque extends Block{ public function isSolid() : bool{ diff --git a/src/block/RedMushroom.php b/src/block/RedMushroom.php index 9be005d17..7e07b83f4 100644 --- a/src/block/RedMushroom.php +++ b/src/block/RedMushroom.php @@ -43,7 +43,11 @@ class RedMushroom extends Flowable{ public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ $down = $this->getSide(Facing::DOWN); - if(!$down->isTransparent()){ + $position = $this->getPosition(); + $lightLevel = $position->getWorld()->getFullLightAt($position->x, $position->y, $position->z); + $downId = $down->getId(); + //TODO: nylium support + if(($lightLevel <= 12 && !$down->isTransparent()) || $downId === BlockLegacyIds::MYCELIUM || $downId === BlockLegacyIds::PODZOL){ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } diff --git a/src/block/Sugarcane.php b/src/block/Sugarcane.php index fe1935c8b..80c758504 100644 --- a/src/block/Sugarcane.php +++ b/src/block/Sugarcane.php @@ -31,6 +31,7 @@ use pocketmine\math\Facing; use pocketmine\math\Vector3; use pocketmine\player\Player; use pocketmine\world\BlockTransaction; +use pocketmine\world\Position; class Sugarcane extends Flowable{ public const MAX_AGE = 15; @@ -49,14 +50,23 @@ class Sugarcane extends Flowable{ return 0b1111; } - private function grow() : bool{ - $grew = false; + private function seekToBottom() : Position{ $world = $this->position->getWorld(); + $bottom = $this->position; + while(($next = $world->getBlock($bottom->down()))->isSameType($this)){ + $bottom = $next->position; + } + return $bottom; + } + + private function grow(Position $pos) : bool{ + $grew = false; + $world = $pos->getWorld(); for($y = 1; $y < 3; ++$y){ - if(!$world->isInWorld($this->position->x, $this->position->y + $y, $this->position->z)){ + if(!$world->isInWorld($pos->x, $pos->y + $y, $pos->z)){ break; } - $b = $world->getBlockAt($this->position->x, $this->position->y + $y, $this->position->z); + $b = $world->getBlockAt($pos->x, $pos->y + $y, $pos->z); if($b->getId() === BlockLegacyIds::AIR){ $ev = new BlockGrowEvent($b, VanillaBlocks::SUGARCANE()); $ev->call(); @@ -65,12 +75,12 @@ class Sugarcane extends Flowable{ } $world->setBlock($b->position, $ev->getNewState()); $grew = true; - }else{ + }elseif(!$b->isSameType($this)){ break; } } $this->age = 0; - $world->setBlock($this->position, $this); + $world->setBlock($pos, $this); return $grew; } @@ -87,7 +97,7 @@ class Sugarcane extends Flowable{ public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ if($item instanceof Fertilizer){ - if(!$this->getSide(Facing::DOWN)->isSameType($this) && $this->grow()){ + if($this->grow($this->seekToBottom())){ $item->pop(); } @@ -111,7 +121,7 @@ class Sugarcane extends Flowable{ public function onRandomTick() : void{ if(!$this->getSide(Facing::DOWN)->isSameType($this)){ if($this->age === self::MAX_AGE){ - $this->grow(); + $this->grow($this->position); }else{ ++$this->age; $this->position->getWorld()->setBlock($this->position, $this); diff --git a/src/block/Thin.php b/src/block/Thin.php index ad5824494..f6e41d43a 100644 --- a/src/block/Thin.php +++ b/src/block/Thin.php @@ -29,6 +29,9 @@ use pocketmine\math\AxisAlignedBB; use pocketmine\math\Facing; use function count; +/** + * Thin blocks behave like glass panes. They connect to full-cube blocks horizontally adjacent to them if possible. + */ class Thin extends Transparent{ /** @var bool[] facing => dummy */ protected array $connections = []; diff --git a/src/block/Transparent.php b/src/block/Transparent.php index b4489cebf..e1ad9588a 100644 --- a/src/block/Transparent.php +++ b/src/block/Transparent.php @@ -23,6 +23,12 @@ declare(strict_types=1); namespace pocketmine\block; +/** + * Transparent blocks do not block any light from propagating through them. + * + * Note: This does **not** imply that the block is **visually** transparent. For example, chests allow light to pass + * through, but the player cannot see through them except at the edges. + */ class Transparent extends Block{ public function isTransparent() : bool{ diff --git a/src/block/UnknownBlock.php b/src/block/UnknownBlock.php index 81a2af259..e81e0ed19 100644 --- a/src/block/UnknownBlock.php +++ b/src/block/UnknownBlock.php @@ -25,6 +25,9 @@ namespace pocketmine\block; use pocketmine\item\Item; +/** + * Represents a block which is unrecognized or not implemented. + */ class UnknownBlock extends Transparent{ public function __construct(BlockIdentifier $idInfo, BlockBreakInfo $breakInfo){ diff --git a/src/block/VanillaBlocks.php b/src/block/VanillaBlocks.php index ec3c5a494..c2216c5dd 100644 --- a/src/block/VanillaBlocks.php +++ b/src/block/VanillaBlocks.php @@ -585,6 +585,7 @@ final class VanillaBlocks{ /** * @return Block[] + * @phpstan-return array */ public static function getAll() : array{ //phpstan doesn't support generic traits yet :( diff --git a/src/block/Vine.php b/src/block/Vine.php index 7706d4f7f..9159315da 100644 --- a/src/block/Vine.php +++ b/src/block/Vine.php @@ -116,7 +116,7 @@ class Vine extends Flowable{ } public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{ - if(!$blockClicked->isSolid() || Facing::axis($face) === Axis::Y){ + if(!$blockClicked->isFullCube() || Facing::axis($face) === Axis::Y){ return false; } diff --git a/src/block/tile/Sign.php b/src/block/tile/Sign.php index 3922e1866..383526bd8 100644 --- a/src/block/tile/Sign.php +++ b/src/block/tile/Sign.php @@ -27,6 +27,7 @@ use pocketmine\block\utils\SignText; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\StringTag; +use pocketmine\utils\Binary; use pocketmine\world\World; use function array_pad; use function array_slice; @@ -42,6 +43,13 @@ use function sprintf; class Sign extends Spawnable{ public const TAG_TEXT_BLOB = "Text"; public const TAG_TEXT_LINE = "Text%d"; //sprintf()able + public const TAG_TEXT_COLOR = "SignTextColor"; + public const TAG_GLOWING_TEXT = "IgnoreLighting"; + /** + * This tag is set to indicate that MCPE-117835 has been addressed in whatever version this sign was created. + * @see https://bugs.mojang.com/browse/MCPE-117835 + */ + public const TAG_LEGACY_BUG_RESOLVE = "TextIgnoreLegacyBugResolved"; /** * @return string[] @@ -111,5 +119,11 @@ class Sign extends Spawnable{ protected function addAdditionalSpawnData(CompoundTag $nbt) : void{ $nbt->setString(self::TAG_TEXT_BLOB, implode("\n", $this->text->getLines())); + + //the following are not yet used by the server, but needed to roll back any changes to glowing state or colour + //if the client uses dye on the sign + $nbt->setInt(self::TAG_TEXT_COLOR, Binary::signInt(0xff_00_00_00)); + $nbt->setByte(self::TAG_GLOWING_TEXT, 0); + $nbt->setByte(self::TAG_LEGACY_BUG_RESOLVE, 1); } } diff --git a/src/block/utils/SignText.php b/src/block/utils/SignText.php index 6f3cb69dd..d3b4a5b9b 100644 --- a/src/block/utils/SignText.php +++ b/src/block/utils/SignText.php @@ -39,7 +39,7 @@ class SignText{ private array $lines; /** - * @param string[]|null $lines index-sensitive; omitting an index will leave it unchanged + * @param string[]|null $lines index-sensitive; keys 0-3 will be used, regardless of array order * * @throws \InvalidArgumentException if the array size is greater than 4 * @throws \InvalidArgumentException if invalid keys (out of bounds or string) are found in the array diff --git a/src/command/Command.php b/src/command/Command.php index a85184777..12a1b8e38 100644 --- a/src/command/Command.php +++ b/src/command/Command.php @@ -27,13 +27,13 @@ declare(strict_types=1); namespace pocketmine\command; use pocketmine\command\utils\CommandException; -use pocketmine\console\ConsoleCommandSender; use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\Translatable; use pocketmine\permission\PermissionManager; use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; +use pocketmine\utils\BroadcastLoggerForwarder; use pocketmine\utils\TextFormat; use function explode; use function str_replace; @@ -227,12 +227,12 @@ abstract class Command{ $result = KnownTranslationFactory::chat_type_admin($source->getName(), $message); $colored = $result->prefix(TextFormat::GRAY . TextFormat::ITALIC); - if($sendToSource && !($source instanceof ConsoleCommandSender)){ + if($sendToSource){ $source->sendMessage($message); } foreach($users as $user){ - if($user instanceof ConsoleCommandSender){ + if($user instanceof BroadcastLoggerForwarder){ $user->sendMessage($result); }elseif($user !== $source){ $user->sendMessage($colored); diff --git a/src/command/SimpleCommandMap.php b/src/command/SimpleCommandMap.php index 5c33bf25a..07afecf6d 100644 --- a/src/command/SimpleCommandMap.php +++ b/src/command/SimpleCommandMap.php @@ -272,10 +272,11 @@ class SimpleCommandMap implements CommandMap{ } //These registered commands have absolute priority + $lowerAlias = strtolower($alias); if(count($targets) > 0){ - $this->knownCommands[strtolower($alias)] = new FormattedCommandAlias(strtolower($alias), $targets); + $this->knownCommands[$lowerAlias] = new FormattedCommandAlias($lowerAlias, $targets); }else{ - unset($this->knownCommands[strtolower($alias)]); + unset($this->knownCommands[$lowerAlias]); } } diff --git a/src/command/defaults/DumpMemoryCommand.php b/src/command/defaults/DumpMemoryCommand.php index 83f177e01..2e425a740 100644 --- a/src/command/defaults/DumpMemoryCommand.php +++ b/src/command/defaults/DumpMemoryCommand.php @@ -25,7 +25,7 @@ namespace pocketmine\command\defaults; use pocketmine\command\CommandSender; use pocketmine\permission\DefaultPermissionNames; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function date; class DumpMemoryCommand extends VanillaCommand{ diff --git a/src/command/defaults/GarbageCollectorCommand.php b/src/command/defaults/GarbageCollectorCommand.php index 69875c7d5..cdfe2b883 100644 --- a/src/command/defaults/GarbageCollectorCommand.php +++ b/src/command/defaults/GarbageCollectorCommand.php @@ -63,7 +63,7 @@ class GarbageCollectorCommand extends VanillaCommand{ $cyclesCollected = $sender->getServer()->getMemoryManager()->triggerGarbageCollector(); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_header()->format(TextFormat::GREEN . "---- " . TextFormat::WHITE, TextFormat::GREEN . " ----" . TextFormat::WHITE)); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_header()->format(TextFormat::GREEN . "---- " . TextFormat::RESET, TextFormat::GREEN . " ----" . TextFormat::RESET)); $sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_chunks(TextFormat::RED . number_format($chunksCollected))->prefix(TextFormat::GOLD)); $sender->sendMessage(KnownTranslationFactory::pocketmine_command_gc_entities(TextFormat::RED . number_format($entitiesCollected))->prefix(TextFormat::GOLD)); diff --git a/src/command/defaults/GiveCommand.php b/src/command/defaults/GiveCommand.php index 650a262d0..b4d3ed372 100644 --- a/src/command/defaults/GiveCommand.php +++ b/src/command/defaults/GiveCommand.php @@ -75,7 +75,11 @@ class GiveCommand extends VanillaCommand{ if(!isset($args[2])){ $item->setCount($item->getMaxStackSize()); }else{ - $item->setCount((int) $args[2]); + $count = $this->getBoundedInt($sender, $args[2], 1, 32767); + if($count === null){ + return true; + } + $item->setCount($count); } if(isset($args[3])){ diff --git a/src/command/defaults/HelpCommand.php b/src/command/defaults/HelpCommand.php index 7fe12e039..4ed5d4275 100644 --- a/src/command/defaults/HelpCommand.php +++ b/src/command/defaults/HelpCommand.php @@ -95,7 +95,7 @@ class HelpCommand extends VanillaCommand{ foreach($commands[$pageNumber - 1] as $command){ $description = $command->getDescription(); $descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description; - $sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getName() . ": " . TextFormat::WHITE . $descriptionString); + $sender->sendMessage(TextFormat::DARK_GREEN . "/" . $command->getName() . ": " . TextFormat::RESET . $descriptionString); } } @@ -107,18 +107,18 @@ class HelpCommand extends VanillaCommand{ $description = $cmd->getDescription(); $descriptionString = $description instanceof Translatable ? $lang->translate($description) : $description; $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_header($cmd->getName()) - ->format(TextFormat::YELLOW . "--------- " . TextFormat::WHITE, TextFormat::YELLOW . " ---------")); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::WHITE . $descriptionString) + ->format(TextFormat::YELLOW . "--------- " . TextFormat::RESET, TextFormat::YELLOW . " ---------")); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_description(TextFormat::RESET . $descriptionString) ->prefix(TextFormat::GOLD)); $usage = $cmd->getUsage(); $usageString = $usage instanceof Translatable ? $lang->translate($usage) : $usage; - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::WHITE . implode("\n" . TextFormat::WHITE, explode("\n", $usageString))) + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_usage(TextFormat::RESET . implode("\n" . TextFormat::RESET, explode("\n", $usageString))) ->prefix(TextFormat::GOLD)); $aliases = $cmd->getAliases(); sort($aliases, SORT_NATURAL); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::WHITE . implode(", ", $aliases)) + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_help_specificCommand_aliases(TextFormat::RESET . implode(", ", $aliases)) ->prefix(TextFormat::GOLD)); return true; diff --git a/src/command/defaults/MeCommand.php b/src/command/defaults/MeCommand.php index 586d6cb39..82bec234f 100644 --- a/src/command/defaults/MeCommand.php +++ b/src/command/defaults/MeCommand.php @@ -52,7 +52,7 @@ class MeCommand extends VanillaCommand{ throw new InvalidCommandSyntaxException(); } - $sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::WHITE . implode(" ", $args))); + $sender->getServer()->broadcastMessage(KnownTranslationFactory::chat_type_emote($sender instanceof Player ? $sender->getDisplayName() : $sender->getName(), TextFormat::RESET . implode(" ", $args))); return true; } diff --git a/src/command/defaults/PluginsCommand.php b/src/command/defaults/PluginsCommand.php index e4c83354d..420b7708c 100644 --- a/src/command/defaults/PluginsCommand.php +++ b/src/command/defaults/PluginsCommand.php @@ -56,7 +56,7 @@ class PluginsCommand extends VanillaCommand{ }, $sender->getServer()->getPluginManager()->getPlugins()); sort($list, SORT_STRING); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_plugins_success((string) count($list), implode(TextFormat::WHITE . ", ", $list))); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_plugins_success((string) count($list), implode(TextFormat::RESET . ", ", $list))); return true; } } diff --git a/src/command/defaults/StatusCommand.php b/src/command/defaults/StatusCommand.php index 7e5f9b544..e97e55f95 100644 --- a/src/command/defaults/StatusCommand.php +++ b/src/command/defaults/StatusCommand.php @@ -52,7 +52,7 @@ class StatusCommand extends VanillaCommand{ $mUsage = Process::getAdvancedMemoryUsage(); $server = $sender->getServer(); - $sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::WHITE . "Server status" . TextFormat::GREEN . " ----"); + $sender->sendMessage(TextFormat::GREEN . "---- " . TextFormat::RESET . "Server status" . TextFormat::GREEN . " ----"); $time = (int) (microtime(true) - $server->getStartTime()); diff --git a/src/command/defaults/TimingsCommand.php b/src/command/defaults/TimingsCommand.php index 14fae62f5..ecfa7b281 100644 --- a/src/command/defaults/TimingsCommand.php +++ b/src/command/defaults/TimingsCommand.php @@ -35,7 +35,7 @@ use pocketmine\timings\TimingsHandler; use pocketmine\utils\InternetException; use pocketmine\utils\InternetRequestResult; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function count; use function fclose; use function file_exists; diff --git a/src/command/defaults/VersionCommand.php b/src/command/defaults/VersionCommand.php index 9fa670957..487f84aff 100644 --- a/src/command/defaults/VersionCommand.php +++ b/src/command/defaults/VersionCommand.php @@ -32,9 +32,7 @@ use pocketmine\utils\TextFormat; use pocketmine\utils\Utils; use pocketmine\VersionInfo; use function count; -use function function_exists; use function implode; -use function opcache_get_status; use function sprintf; use function stripos; use function strtolower; @@ -59,36 +57,31 @@ class VersionCommand extends VanillaCommand{ if(count($args) === 0){ $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_serverSoftwareName( - VersionInfo::NAME + TextFormat::GREEN . VersionInfo::NAME . TextFormat::RESET )); + $versionColor = VersionInfo::IS_DEVELOPMENT_BUILD ? TextFormat::YELLOW : TextFormat::GREEN; $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_serverSoftwareVersion( - VersionInfo::VERSION()->getFullVersion(), - VersionInfo::GIT_HASH() + $versionColor . VersionInfo::VERSION()->getFullVersion() . TextFormat::RESET, + TextFormat::GREEN . VersionInfo::GIT_HASH() . TextFormat::RESET )); $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_minecraftVersion( - ProtocolInfo::MINECRAFT_VERSION_NETWORK, - (string) ProtocolInfo::CURRENT_PROTOCOL + TextFormat::GREEN . ProtocolInfo::MINECRAFT_VERSION_NETWORK . TextFormat::RESET, + TextFormat::GREEN . ProtocolInfo::CURRENT_PROTOCOL . TextFormat::RESET )); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpVersion(PHP_VERSION)); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpVersion(TextFormat::GREEN . PHP_VERSION . TextFormat::RESET)); - if( - function_exists('opcache_get_status') && - ($opcacheStatus = opcache_get_status(false)) !== false && - isset($opcacheStatus["jit"]["on"]) - ){ - $jit = $opcacheStatus["jit"]; - if($jit["on"] === true){ - $jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitEnabled( - sprintf("CRTO: %s%s%s%s", $jit["opt_flags"] >> 2, $jit["opt_flags"] & 0x03, $jit["kind"], $jit["opt_level"]) - ); + $jitMode = Utils::getOpcacheJitMode(); + if($jitMode !== null){ + if($jitMode !== 0){ + $jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitEnabled(sprintf("CRTO: %d", $jitMode)); }else{ $jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitDisabled(); } }else{ $jitStatus = KnownTranslationFactory::pocketmine_command_version_phpJitNotSupported(); } - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpJitStatus($jitStatus)); - $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_operatingSystem(Utils::getOS())); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_phpJitStatus($jitStatus->format(TextFormat::GREEN, TextFormat::RESET))); + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_version_operatingSystem(TextFormat::GREEN . Utils::getOS() . TextFormat::RESET)); }else{ $pluginName = implode(" ", $args); $exactPlugin = $sender->getServer()->getPluginManager()->getPlugin($pluginName); @@ -118,7 +111,7 @@ class VersionCommand extends VanillaCommand{ private function describeToSender(Plugin $plugin, CommandSender $sender) : void{ $desc = $plugin->getDescription(); - $sender->sendMessage(TextFormat::DARK_GREEN . $desc->getName() . TextFormat::WHITE . " version " . TextFormat::DARK_GREEN . $desc->getVersion()); + $sender->sendMessage(TextFormat::DARK_GREEN . $desc->getName() . TextFormat::RESET . " version " . TextFormat::DARK_GREEN . $desc->getVersion()); if($desc->getDescription() !== ""){ $sender->sendMessage($desc->getDescription()); diff --git a/src/console/ConsoleCommandSender.php b/src/console/ConsoleCommandSender.php index 30035ac3e..ef3683b0b 100644 --- a/src/console/ConsoleCommandSender.php +++ b/src/console/ConsoleCommandSender.php @@ -30,6 +30,8 @@ use pocketmine\permission\DefaultPermissions; use pocketmine\permission\PermissibleBase; use pocketmine\permission\PermissibleDelegateTrait; use pocketmine\Server; +use pocketmine\utils\Terminal; +use pocketmine\utils\TextFormat; use function explode; use function trim; use const PHP_INT_MAX; @@ -59,13 +61,12 @@ class ConsoleCommandSender implements CommandSender{ } public function sendMessage(Translatable|string $message) : void{ - $server = $this->getServer(); if($message instanceof Translatable){ $message = $this->getLanguage()->translate($message); } foreach(explode("\n", trim($message)) as $line){ - $server->getLogger()->info($line); + Terminal::writeLine(TextFormat::GREEN . "Command output | " . TextFormat::addBase(TextFormat::WHITE, $line)); } } diff --git a/src/console/ConsoleReaderChildProcess.php b/src/console/ConsoleReaderChildProcess.php index 5bf2ff71f..2d4e3fc56 100644 --- a/src/console/ConsoleReaderChildProcess.php +++ b/src/console/ConsoleReaderChildProcess.php @@ -23,12 +23,14 @@ declare(strict_types=1); namespace pocketmine\console; +use pocketmine\utils\Process; use function cli_set_process_title; use function count; use function dirname; use function feof; use function fwrite; use function stream_socket_client; +use const PTHREADS_INHERIT_NONE; require dirname(__DIR__, 2) . '/vendor/autoload.php'; @@ -43,9 +45,40 @@ $socket = stream_socket_client($argv[1], $errCode, $errMessage, 15.0); if($socket === false){ throw new \RuntimeException("Failed to connect to server process ($errCode): $errMessage"); } -$consoleReader = new ConsoleReader(); + +$channel = new \Threaded(); +$thread = new class($channel) extends \Thread{ + public function __construct( + private \Threaded $channel, + ){} + + public function run(){ + require dirname(__DIR__, 2) . '/vendor/autoload.php'; + + $channel = $this->channel; + $reader = new ConsoleReader(); + while(true){ // @phpstan-ignore-line + $line = $reader->readLine(); + if($line !== null){ + $channel->synchronized(function() use ($channel, $line) : void{ + $channel[] = $line; + $channel->notify(); + }); + } + } + } +}; + +$thread->start(PTHREADS_INHERIT_NONE); while(!feof($socket)){ - $line = $consoleReader->readLine(); + $line = $channel->synchronized(function() use ($channel) : ?string{ + if(count($channel) === 0){ + $channel->wait(1_000_000); + } + /** @var string|null $line */ + $line = $channel->shift(); + return $line; + }); if(@fwrite($socket, ($line ?? "") . "\n") === false){ //Always send even if there's no line, to check if the parent is alive //If the parent process was terminated forcibly, it won't close the connection properly, so feof() will return @@ -53,3 +86,8 @@ while(!feof($socket)){ break; } } + +//For simplicity's sake, we don't bother with a graceful shutdown here. +//The parent process would normally forcibly terminate the child process anyway, so we only reach this point if the +//parent process was terminated forcibly and didn't clean up after itself. +Process::kill(Process::pid(), false); diff --git a/src/console/ConsoleReaderThread.php b/src/console/ConsoleReaderChildProcessDaemon.php similarity index 50% rename from src/console/ConsoleReaderThread.php rename to src/console/ConsoleReaderChildProcessDaemon.php index eac19ef84..138559f06 100644 --- a/src/console/ConsoleReaderThread.php +++ b/src/console/ConsoleReaderChildProcessDaemon.php @@ -23,11 +23,9 @@ declare(strict_types=1); namespace pocketmine\console; -use pocketmine\snooze\SleeperNotifier; -use pocketmine\thread\Thread; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function base64_encode; use function fgets; use function fopen; @@ -45,36 +43,35 @@ use function trim; use const PHP_BINARY; use const STREAM_SHUT_RDWR; -final class ConsoleReaderThread extends Thread{ +/** + * This pile of shit exists because PHP on Windows is broken, and can't handle stream_select() on stdin or pipes + * properly - stdin native triggers stream_select() when a key is pressed, causing it to get stuck in fgets() + * waiting for a line that might never come (and Windows doesn't support character-based reading either), and + * pipes just constantly trigger stream_select() instead of only when data is returned, rendering it useless. + * + * This results in whichever process reads stdin getting stuck on shutdown, which previously forced us to kill + * the entire server process to make it go away. + * + * To get around this problem, we delegate the responsibility of reading stdin to a subprocess, which we can + * then brutally murder when the server shuts down, without killing the entire server process. + * Thankfully, stream_select() actually works properly on sockets, so we can use them for inter-process + * communication. + */ +final class ConsoleReaderChildProcessDaemon{ + private \PrefixedLogger $logger; + /** @var resource */ + private $subprocess; + /** @var resource */ + private $socket; + public function __construct( - private \Threaded $buffer, - private ?SleeperNotifier $notifier = null - ){} - - protected function onRun() : void{ - $buffer = $this->buffer; - $notifier = $this->notifier; - - while(!$this->isKilled){ - $this->runSubprocess($buffer, $notifier); - } + \Logger $logger + ){ + $this->logger = new \PrefixedLogger($logger, "Console Reader Daemon"); + $this->prepareSubprocess(); } - /** - * This pile of shit exists because PHP on Windows is broken, and can't handle stream_select() on stdin or pipes - * properly - stdin native triggers stream_select() when a key is pressed, causing it to get stuck in fgets() - * waiting for a line that might never come (and Windows doesn't support character-based reading either), and - * pipes just constantly trigger stream_select() instead of only when data is returned, rendering it useless. - * - * This results in whichever process reads stdin getting stuck on shutdown, which previously forced us to kill - * the entire server process to make it go away. - * - * To get around this problem, we delegate the responsibility of reading stdin to a subprocess, which we can - * then brutally murder when the server shuts down, without killing the entire server process. - * Thankfully, stream_select() actually works properly on sockets, so we can use them for inter-process - * communication. - */ - private function runSubprocess(\Threaded $buffer, ?SleeperNotifier $notifier) : void{ + private function prepareSubprocess() : void{ $server = stream_socket_server("tcp://127.0.0.1:0"); if($server === false){ throw new \RuntimeException("Failed to open console reader socket server"); @@ -96,41 +93,43 @@ final class ConsoleReaderThread extends Thread{ throw new AssumptionFailedError("stream_socket_accept() returned false"); } stream_socket_shutdown($server, STREAM_SHUT_RDWR); - while(!$this->isKilled){ - $r = [$client]; - $w = null; - $e = null; - if(stream_select($r, $w, $e, 0, 200000) === 1){ - $command = fgets($client); - if($command === false){ - //subprocess died for some reason; this could be someone killed it manually from outside (e.g. - //mistyped PID) or it might be a ctrl+c signal to this process that the child is handling - //differently (different signal handlers). - //since we have no way to know the difference, we just kill the sub and start a new one. - break; - } - $command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid"); - $command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid"); - if($command === ""){ - continue; - } - $buffer[] = $command; - if($notifier !== null){ - $notifier->wakeupSleeper(); - } - } - } + $this->subprocess = $sub; + $this->socket = $client; + } + private function shutdownSubprocess() : void{ //we have no way to signal to the subprocess to shut down gracefully; besides, Windows sucks, and the subprocess //gets stuck in a blocking fgets() read because stream_select() is a hunk of junk (hence the separate process in //the first place). - proc_terminate($sub); - proc_close($sub); - stream_socket_shutdown($client, STREAM_SHUT_RDWR); + proc_terminate($this->subprocess); + proc_close($this->subprocess); + stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR); } - public function getThreadName() : string{ - return "Console"; + public function readLine() : ?string{ + $r = [$this->socket]; + $w = null; + $e = null; + if(stream_select($r, $w, $e, 0, 0) === 1){ + $command = fgets($this->socket); + if($command === false){ + $this->logger->debug("Lost connection to subprocess, restarting (maybe the child process was killed from outside?)"); + $this->shutdownSubprocess(); + $this->prepareSubprocess(); + return null; + } + + $command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid"); + $command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid"); + + return $command !== "" ? $command : null; + } + + return null; + } + + public function quit() : void{ + $this->shutdownSubprocess(); } } diff --git a/src/crash/CrashDump.php b/src/crash/CrashDump.php index 101e35201..e743a6704 100644 --- a/src/crash/CrashDump.php +++ b/src/crash/CrashDump.php @@ -33,7 +33,7 @@ use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; use pocketmine\VersionInfo; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function base64_encode; use function error_get_last; use function file; @@ -164,6 +164,8 @@ class CrashDump{ } $this->data->extensions = $extensions; + $this->data->jit_mode = Utils::getOpcacheJitMode(); + if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){ ob_start(); phpinfo(); diff --git a/src/crash/CrashDumpData.php b/src/crash/CrashDumpData.php index 264f67d67..0f5358be5 100644 --- a/src/crash/CrashDumpData.php +++ b/src/crash/CrashDumpData.php @@ -66,6 +66,8 @@ final class CrashDumpData implements \JsonSerializable{ */ public array $extensions = []; + public ?int $jit_mode = null; + public string $phpinfo = ""; public CrashDumpDataGeneral $general; diff --git a/src/data/bedrock/DyeColorIdMap.php b/src/data/bedrock/DyeColorIdMap.php index 112ffe937..279c90ebb 100644 --- a/src/data/bedrock/DyeColorIdMap.php +++ b/src/data/bedrock/DyeColorIdMap.php @@ -74,7 +74,7 @@ final class DyeColorIdMap{ } public function fromId(int $id) : ?DyeColor{ - return $this->idToEnum[$id]; + return $this->idToEnum[$id] ?? null; } public function fromInvertedId(int $id) : ?DyeColor{ diff --git a/src/data/bedrock/LegacyBiomeIdToStringIdMap.php b/src/data/bedrock/LegacyBiomeIdToStringIdMap.php index 974792eba..eba0034e8 100644 --- a/src/data/bedrock/LegacyBiomeIdToStringIdMap.php +++ b/src/data/bedrock/LegacyBiomeIdToStringIdMap.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\data\bedrock; use pocketmine\utils\SingletonTrait; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; final class LegacyBiomeIdToStringIdMap extends LegacyToStringBidirectionalIdMap{ use SingletonTrait; diff --git a/src/data/bedrock/LegacyBlockIdToStringIdMap.php b/src/data/bedrock/LegacyBlockIdToStringIdMap.php index 616c56bcf..35c24caf9 100644 --- a/src/data/bedrock/LegacyBlockIdToStringIdMap.php +++ b/src/data/bedrock/LegacyBlockIdToStringIdMap.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\data\bedrock; use pocketmine\utils\SingletonTrait; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; final class LegacyBlockIdToStringIdMap extends LegacyToStringBidirectionalIdMap{ use SingletonTrait; diff --git a/src/data/bedrock/LegacyEntityIdToStringIdMap.php b/src/data/bedrock/LegacyEntityIdToStringIdMap.php index 2e3e4aecc..8427e4594 100644 --- a/src/data/bedrock/LegacyEntityIdToStringIdMap.php +++ b/src/data/bedrock/LegacyEntityIdToStringIdMap.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\data\bedrock; use pocketmine\utils\SingletonTrait; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; final class LegacyEntityIdToStringIdMap extends LegacyToStringBidirectionalIdMap{ use SingletonTrait; diff --git a/src/data/bedrock/LegacyItemIdToStringIdMap.php b/src/data/bedrock/LegacyItemIdToStringIdMap.php index 85b6ff1bf..254ad96bb 100644 --- a/src/data/bedrock/LegacyItemIdToStringIdMap.php +++ b/src/data/bedrock/LegacyItemIdToStringIdMap.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\data\bedrock; use pocketmine\utils\SingletonTrait; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; final class LegacyItemIdToStringIdMap extends LegacyToStringBidirectionalIdMap{ use SingletonTrait; diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 54873f9f1..3e34d5c26 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -249,10 +249,8 @@ abstract class Entity{ $this->getWorld()->addEntity($this); $this->lastUpdate = $this->server->getTick(); - (new EntitySpawnEvent($this))->call(); $this->scheduleUpdate(); - } abstract protected function getInitialSizeInfo() : EntitySizeInfo; @@ -937,6 +935,14 @@ abstract class Entity{ return (new Vector2(-cos(deg2rad($this->location->yaw) - M_PI_2), -sin(deg2rad($this->location->yaw) - M_PI_2)))->normalize(); } + /** + * Called from onUpdate() on the first tick of a new entity. This is called before any movement processing or + * main ticking logic. Use this to fire any events related to spawning the entity. + */ + protected function onFirstUpdate(int $currentTick) : void{ + (new EntitySpawnEvent($this))->call(); + } + public function onUpdate(int $currentTick) : bool{ if($this->closed){ return false; @@ -953,6 +959,10 @@ abstract class Entity{ $this->lastUpdate = $currentTick; + if($this->justCreated){ + $this->onFirstUpdate($currentTick); + } + if(!$this->isAlive()){ if($this->onDeathUpdate($tickDiff)){ $this->flagForDespawn(); diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php index 90b3f659f..fb3a1f33d 100644 --- a/src/entity/EntityFactory.php +++ b/src/entity/EntityFactory.php @@ -175,20 +175,6 @@ final class EntityFactory{ }, ['Human']); } - /** - * @phpstan-param \Closure(World, CompoundTag) : Entity $creationFunc - */ - private static function validateCreationFunc(\Closure $creationFunc) : void{ - $sig = new CallbackType( - new ReturnType(Entity::class), - new ParameterType("world", World::class), - new ParameterType("nbt", CompoundTag::class) - ); - if(!$sig->isSatisfiedBy($creationFunc)){ - throw new \TypeError("Declaration of callable `" . CallbackType::createFromCallable($creationFunc) . "` must be compatible with `" . $sig . "`"); - } - } - /** * Registers an entity type into the index. * @@ -207,7 +193,11 @@ final class EntityFactory{ throw new \InvalidArgumentException("At least one save name must be provided"); } Utils::testValidInstance($className, Entity::class); - self::validateCreationFunc($creationFunc); + Utils::validateCallableSignature(new CallbackType( + new ReturnType(Entity::class), + new ParameterType("world", World::class), + new ParameterType("nbt", CompoundTag::class) + ), $creationFunc); foreach($saveNames as $name){ $this->creationFuncs[$name] = $creationFunc; @@ -227,7 +217,7 @@ final class EntityFactory{ */ public function createFromData(World $world, CompoundTag $nbt) : ?Entity{ try{ - $saveId = $nbt->getTag("id") ?? $nbt->getTag("identifier"); + $saveId = $nbt->getTag("identifier") ?? $nbt->getTag("id"); $func = null; if($saveId instanceof StringTag){ $func = $this->creationFuncs[$saveId->getValue()] ?? null; @@ -248,7 +238,7 @@ final class EntityFactory{ public function injectSaveId(string $class, CompoundTag $saveData) : void{ if(isset($this->saveNames[$class])){ - $saveData->setTag("id", new StringTag($this->saveNames[$class])); + $saveData->setTag("identifier", new StringTag($this->saveNames[$class])); }else{ throw new \InvalidArgumentException("Entity $class is not registered"); } diff --git a/src/entity/ExperienceManager.php b/src/entity/ExperienceManager.php index b74b87192..9cff48f33 100644 --- a/src/entity/ExperienceManager.php +++ b/src/entity/ExperienceManager.php @@ -237,11 +237,10 @@ class ExperienceManager{ } public function onPickupXp(int $xpValue) : void{ - static $mainHandIndex = -1; - static $offHandIndex = -2; + $mainHandIndex = -1; + $offHandIndex = -2; //TODO: replace this with a more generic equipment getting/setting interface - /** @var Durable[] $equipment */ $equipment = []; if(($item = $this->entity->getInventory()->getItemInHand()) instanceof Durable && $item->hasEnchantment(VanillaEnchantments::MENDING())){ diff --git a/src/entity/Living.php b/src/entity/Living.php index 1d597715c..aa4690068 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -56,6 +56,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; use pocketmine\player\Player; use pocketmine\timings\Timings; use pocketmine\utils\Binary; +use pocketmine\world\sound\BurpSound; use pocketmine\world\sound\EntityLandSound; use pocketmine\world\sound\EntityLongFallSound; use pocketmine\world\sound\EntityShortFallSound; @@ -255,8 +256,7 @@ abstract class Living extends Entity{ $size = $this->getInitialSizeInfo(); if($this->isSwimming() || $this->isGliding()){ $width = $size->getWidth(); - //we don't actually know an appropriate eye height for a swimming mob, but 2/3 should be good enough. - $this->setSize((new EntitySizeInfo($width, $width, $width * 2 / 3))->scale($this->getScale())); + $this->setSize((new EntitySizeInfo($width, $width, $width * 0.9))->scale($this->getScale())); }else{ $this->setSize($size->scale($this->getScale())); } @@ -293,6 +293,10 @@ abstract class Living extends Entity{ return $nbt; } + /** + * @deprecated This function always returns true, no matter whether the target is in the line of sight or not. + * @see VoxelRayTrace::inDirection() for a more generalized method of ray-tracing to a target. + */ public function hasLineOfSight(Entity $entity) : bool{ //TODO: head height return true; @@ -320,6 +324,9 @@ abstract class Living extends Entity{ foreach($consumable->getAdditionalEffects() as $effect){ $this->effectManager->add($effect); } + if($consumable instanceof FoodSource){ + $this->broadcastSound(new BurpSound()); + } $consumable->onConsume($this); } diff --git a/src/entity/effect/Effect.php b/src/entity/effect/Effect.php index 442028d4c..cce009733 100644 --- a/src/entity/effect/Effect.php +++ b/src/entity/effect/Effect.php @@ -38,7 +38,6 @@ class Effect{ * @param Translatable|string $name Translation key used for effect name * @param Color $color Color of bubbles given by this effect * @param bool $bad Whether the effect is harmful - * @param int $defaultDuration * @param bool $hasBubbles Whether the effect has potion bubbles. Some do not (e.g. Instant Damage has its own particles instead of bubbles) */ public function __construct( diff --git a/src/entity/effect/HealthBoostEffect.php b/src/entity/effect/HealthBoostEffect.php index 90c5ee680..022015d42 100644 --- a/src/entity/effect/HealthBoostEffect.php +++ b/src/entity/effect/HealthBoostEffect.php @@ -33,5 +33,8 @@ class HealthBoostEffect extends Effect{ public function remove(Living $entity, EffectInstance $instance) : void{ $entity->setMaxHealth($entity->getMaxHealth() - 4 * $instance->getEffectLevel()); + if($entity->getHealth() > $entity->getMaxHealth()){ + $entity->setHealth($entity->getMaxHealth()); + } } } diff --git a/src/entity/effect/InstantDamageEffect.php b/src/entity/effect/InstantDamageEffect.php index 9461fd979..28d731079 100644 --- a/src/entity/effect/InstantDamageEffect.php +++ b/src/entity/effect/InstantDamageEffect.php @@ -33,7 +33,7 @@ class InstantDamageEffect extends InstantEffect{ public function applyEffect(Living $entity, EffectInstance $instance, float $potency = 1.0, ?Entity $source = null) : void{ //TODO: add particles (witch spell) - $damage = (4 << $instance->getAmplifier()) * $potency; + $damage = (6 << $instance->getAmplifier()) * $potency; if($source !== null){ $sourceOwner = $source->getOwningEntity(); if($sourceOwner !== null){ diff --git a/src/entity/effect/RegenerationEffect.php b/src/entity/effect/RegenerationEffect.php index 28ba2b18b..f06ce67db 100644 --- a/src/entity/effect/RegenerationEffect.php +++ b/src/entity/effect/RegenerationEffect.php @@ -30,7 +30,7 @@ use pocketmine\event\entity\EntityRegainHealthEvent; class RegenerationEffect extends Effect{ public function canTick(EffectInstance $instance) : bool{ - if(($interval = (40 >> $instance->getAmplifier())) > 0){ + if(($interval = (50 >> $instance->getAmplifier())) > 0){ return ($instance->getDuration() % $interval) === 0; } return true; diff --git a/src/entity/effect/VanillaEffects.php b/src/entity/effect/VanillaEffects.php index 249ce367c..04f7985da 100644 --- a/src/entity/effect/VanillaEffects.php +++ b/src/entity/effect/VanillaEffects.php @@ -101,6 +101,7 @@ final class VanillaEffects{ /** * @return Effect[] + * @phpstan-return array */ public static function getAll() : array{ //phpstan doesn't support generic traits yet :( diff --git a/src/entity/object/FallingBlock.php b/src/entity/object/FallingBlock.php index 5d3018d4b..b9af0237c 100644 --- a/src/entity/object/FallingBlock.php +++ b/src/entity/object/FallingBlock.php @@ -117,7 +117,6 @@ class FallingBlock extends Entity{ $block = $world->getBlock($pos); if(!$block->canBeReplaced() || !$world->isInWorld($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ()) || ($this->onGround && abs($this->location->y - $this->location->getFloorY()) > 0.001)){ - //FIXME: anvils are supposed to destroy torches $world->dropItem($this->location, $this->block->asItem()); }else{ $ev = new EntityBlockChangeEvent($this, $block, $blockTarget ?? $this->block); diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php index 7556e843d..4fe844f7e 100644 --- a/src/entity/object/ItemEntity.php +++ b/src/entity/object/ItemEntity.php @@ -92,8 +92,11 @@ class ItemEntity extends Entity{ $this->pickupDelay = $nbt->getShort("PickupDelay", $this->pickupDelay); $this->owner = $nbt->getString("Owner", $this->owner); $this->thrower = $nbt->getString("Thrower", $this->thrower); + } - (new ItemSpawnEvent($this))->call(); + protected function onFirstUpdate(int $currentTick) : void{ + (new ItemSpawnEvent($this))->call(); //this must be called before EntitySpawnEvent, to maintain backwards compatibility + parent::onFirstUpdate($currentTick); } protected function entityBaseTick(int $tickDiff = 1) : bool{ @@ -186,6 +189,10 @@ class ItemEntity extends Entity{ return true; } + public function canSaveWithChunk() : bool{ + return !$this->item->isNull() && parent::canSaveWithChunk(); + } + public function saveNBT() : CompoundTag{ $nbt = parent::saveNBT(); $nbt->setTag("Item", $this->item->nbtSerialize()); diff --git a/src/entity/projectile/SplashPotion.php b/src/entity/projectile/SplashPotion.php index f9cee30a9..a942d9622 100644 --- a/src/entity/projectile/SplashPotion.php +++ b/src/entity/projectile/SplashPotion.php @@ -98,8 +98,8 @@ class SplashPotion extends Throwable{ if($hasEffects){ if(!$this->willLinger()){ - foreach($this->getWorld()->getNearbyEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){ - if($entity instanceof Living && $entity->isAlive()){ + foreach($this->getWorld()->getCollidingEntities($this->boundingBox->expandedCopy(4.125, 2.125, 4.125), $this) as $entity){ + if($entity instanceof Living){ $distanceSquared = $entity->getEyePos()->distanceSquared($this->location); if($distanceSquared > 16){ //4 blocks continue; diff --git a/src/event/block/BlockBreakEvent.php b/src/event/block/BlockBreakEvent.php index 2b83478af..fba60983e 100644 --- a/src/event/block/BlockBreakEvent.php +++ b/src/event/block/BlockBreakEvent.php @@ -72,7 +72,7 @@ class BlockBreakEvent extends BlockEvent implements Cancellable{ * Returns the item used to destroy the block. */ public function getItem() : Item{ - return $this->item; + return clone $this->item; } /** diff --git a/src/event/block/BlockPlaceEvent.php b/src/event/block/BlockPlaceEvent.php index 28b667573..0c134eb8c 100644 --- a/src/event/block/BlockPlaceEvent.php +++ b/src/event/block/BlockPlaceEvent.php @@ -65,7 +65,7 @@ class BlockPlaceEvent extends BlockEvent implements Cancellable{ * Gets the item in hand */ public function getItem() : Item{ - return $this->item; + return clone $this->item; } public function getBlockReplaced() : Block{ diff --git a/src/event/player/PlayerInteractEvent.php b/src/event/player/PlayerInteractEvent.php index e697d4fdc..f28391364 100644 --- a/src/event/player/PlayerInteractEvent.php +++ b/src/event/player/PlayerInteractEvent.php @@ -69,7 +69,7 @@ class PlayerInteractEvent extends PlayerEvent implements Cancellable{ } public function getItem() : Item{ - return $this->item; + return clone $this->item; } public function getBlock() : Block{ diff --git a/src/event/player/PlayerItemHeldEvent.php b/src/event/player/PlayerItemHeldEvent.php index 3b0392d4c..3e69298dd 100644 --- a/src/event/player/PlayerItemHeldEvent.php +++ b/src/event/player/PlayerItemHeldEvent.php @@ -60,6 +60,6 @@ class PlayerItemHeldEvent extends PlayerEvent implements Cancellable{ * Returns the item in the slot that the player is trying to equip. */ public function getItem() : Item{ - return $this->item; + return clone $this->item; } } diff --git a/src/event/player/PlayerItemUseEvent.php b/src/event/player/PlayerItemUseEvent.php index 3cd1dfa46..f8abfcd9c 100644 --- a/src/event/player/PlayerItemUseEvent.php +++ b/src/event/player/PlayerItemUseEvent.php @@ -47,7 +47,7 @@ class PlayerItemUseEvent extends PlayerEvent implements Cancellable{ * Returns the item used. */ public function getItem() : Item{ - return $this->item; + return clone $this->item; } /** diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 4f939c518..3edc52043 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -76,11 +76,13 @@ abstract class BaseInventory implements Inventory{ /** * @param Item[] $items + * @phpstan-param array $items */ abstract protected function internalSetContents(array $items) : void; /** * @param Item[] $items + * @phpstan-param array $items */ public function setContents(array $items) : void{ if(count($items) > $this->getSize()){ @@ -132,6 +134,7 @@ abstract class BaseInventory implements Inventory{ return $slots; } + public function first(Item $item, bool $exact = false) : int{ $count = $exact ? $item->getCount() : max(1, $item->getCount()); $checkDamage = $exact || !$item->hasAnyDamageValue(); diff --git a/src/inventory/CreativeInventory.php b/src/inventory/CreativeInventory.php index 40b3075d2..980c2e35f 100644 --- a/src/inventory/CreativeInventory.php +++ b/src/inventory/CreativeInventory.php @@ -26,7 +26,7 @@ namespace pocketmine\inventory; use pocketmine\item\Durable; use pocketmine\item\Item; use pocketmine\utils\SingletonTrait; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function file_get_contents; use function json_decode; diff --git a/src/inventory/DelegateInventory.php b/src/inventory/DelegateInventory.php index db65b7a16..391b9599c 100644 --- a/src/inventory/DelegateInventory.php +++ b/src/inventory/DelegateInventory.php @@ -24,8 +24,6 @@ declare(strict_types=1); namespace pocketmine\inventory; use pocketmine\item\Item; -use pocketmine\player\Player; -use function count; /** * An inventory which is backed by another inventory, and acts as a proxy to that inventory. @@ -37,16 +35,25 @@ class DelegateInventory extends BaseInventory{ private Inventory $backingInventory ){ parent::__construct(); + $weakThis = \WeakReference::create($this); $this->backingInventory->getListeners()->add($this->inventoryListener = new CallbackInventoryListener( - function(Inventory $unused, int $slot, Item $oldItem) : void{ - $this->onSlotChange($slot, $oldItem); + static function(Inventory $unused, int $slot, Item $oldItem) use ($weakThis) : void{ + if(($strongThis = $weakThis->get()) !== null){ + $strongThis->onSlotChange($slot, $oldItem); + } }, - function(Inventory $unused, array $oldContents) : void{ - $this->onContentChange($oldContents); + static function(Inventory $unused, array $oldContents) use ($weakThis) : void{ + if(($strongThis = $weakThis->get()) !== null){ + $strongThis->onContentChange($oldContents); + } } )); } + public function __destruct(){ + $this->backingInventory->getListeners()->remove($this->inventoryListener); + } + public function getSize() : int{ return $this->backingInventory->getSize(); } @@ -66,12 +73,4 @@ class DelegateInventory extends BaseInventory{ protected function internalSetContents(array $items) : void{ $this->backingInventory->setContents($items); } - - public function onClose(Player $who) : void{ - parent::onClose($who); - if(count($this->getViewers()) === 0 && count($this->getListeners()->toArray()) === 1){ - $this->backingInventory->getListeners()->remove($this->inventoryListener); - $this->inventoryListener = CallbackInventoryListener::onAnyChange(static function() : void{}); //break cyclic reference - } - } } diff --git a/src/inventory/Inventory.php b/src/inventory/Inventory.php index fa45d4a78..230f6aa49 100644 --- a/src/inventory/Inventory.php +++ b/src/inventory/Inventory.php @@ -47,12 +47,20 @@ interface Inventory{ public function setItem(int $index, Item $item) : void; /** + * Returns an array of all the itemstacks in the inventory, indexed by their slot number. + * Empty slots are not included unless includeEmpty is true. + * * @return Item[] + * @phpstan-return array */ public function getContents(bool $includeEmpty = false) : array; /** + * Sets the contents of the inventory. Non-numeric offsets or offsets larger than the size of the inventory are + * ignored. + * * @param Item[] $items + * @phpstan-param array $items */ public function setContents(array $items) : void; @@ -85,8 +93,10 @@ interface Inventory{ /** * Will return all the Items that has the same id and metadata (if not null). * Won't check amount + * The returned array is indexed by slot number. * * @return Item[] + * @phpstan-return array */ public function all(Item $item) : array; diff --git a/src/inventory/SimpleInventory.php b/src/inventory/SimpleInventory.php index 95ed29c24..aae11c84c 100644 --- a/src/inventory/SimpleInventory.php +++ b/src/inventory/SimpleInventory.php @@ -58,6 +58,7 @@ class SimpleInventory extends BaseInventory{ /** * @return Item[] + * @phpstan-return array */ public function getContents(bool $includeEmpty = false) : array{ $contents = []; diff --git a/src/inventory/transaction/action/DropItemAction.php b/src/inventory/transaction/action/DropItemAction.php index 896d8fbe9..636317fcf 100644 --- a/src/inventory/transaction/action/DropItemAction.php +++ b/src/inventory/transaction/action/DropItemAction.php @@ -42,6 +42,9 @@ class DropItemAction extends InventoryAction{ if($this->targetItem->isNull()){ throw new TransactionValidationException("Cannot drop an empty itemstack"); } + if($this->targetItem->getCount() > $this->targetItem->getMaxStackSize()){ + throw new TransactionValidationException("Target item exceeds item type max stack size"); + } } public function onPreExecute(Player $source) : bool{ diff --git a/src/inventory/transaction/action/SlotChangeAction.php b/src/inventory/transaction/action/SlotChangeAction.php index 2bb445773..d4130e592 100644 --- a/src/inventory/transaction/action/SlotChangeAction.php +++ b/src/inventory/transaction/action/SlotChangeAction.php @@ -70,6 +70,12 @@ class SlotChangeAction extends InventoryAction{ if(!$this->inventory->getItem($this->inventorySlot)->equalsExact($this->sourceItem)){ throw new TransactionValidationException("Slot does not contain expected original item"); } + if($this->targetItem->getCount() > $this->targetItem->getMaxStackSize()){ + throw new TransactionValidationException("Target item exceeds item type max stack size"); + } + if($this->targetItem->getCount() > $this->inventory->getMaxStackSize()){ + throw new TransactionValidationException("Target item exceeds inventory max stack size"); + } } /** diff --git a/src/item/LegacyStringToItemParser.php b/src/item/LegacyStringToItemParser.php index e05f4d031..9ec97ede8 100644 --- a/src/item/LegacyStringToItemParser.php +++ b/src/item/LegacyStringToItemParser.php @@ -26,7 +26,7 @@ namespace pocketmine\item; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\SingletonTrait; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function explode; use function file_get_contents; use function is_array; @@ -108,8 +108,9 @@ final class LegacyStringToItemParser{ throw new LegacyStringToItemParserException("Unable to parse \"" . $b[1] . "\" from \"" . $input . "\" as a valid meta value"); } - if(isset($this->map[strtolower($b[0])])){ - $item = $this->itemFactory->get($this->map[strtolower($b[0])], $meta); + $id = strtolower($b[0]); + if(isset($this->map[$id])){ + $item = $this->itemFactory->get($this->map[$id], $meta); }else{ throw new LegacyStringToItemParserException("Unable to resolve \"" . $input . "\" to a valid item"); } diff --git a/src/item/VanillaItems.php b/src/item/VanillaItems.php index 98a4ba514..9bc537812 100644 --- a/src/item/VanillaItems.php +++ b/src/item/VanillaItems.php @@ -384,6 +384,7 @@ final class VanillaItems{ /** * @return Item[] + * @phpstan-return array */ public static function getAll() : array{ //phpstan doesn't support generic traits yet :( diff --git a/src/lang/Language.php b/src/lang/Language.php index 7e992d323..3c61a0cdd 100644 --- a/src/lang/Language.php +++ b/src/lang/Language.php @@ -24,9 +24,10 @@ declare(strict_types=1); namespace pocketmine\lang; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function array_filter; use function array_map; +use function count; use function explode; use function file_exists; use function is_dir; @@ -67,10 +68,14 @@ class Language{ $result = []; foreach($files as $file){ - $code = explode(".", $file)[0]; - $strings = self::loadLang($path, $code); - if(isset($strings["language.name"])){ - $result[$code] = $strings["language.name"]; + try{ + $code = explode(".", $file)[0]; + $strings = self::loadLang($path, $code); + if(isset($strings["language.name"])){ + $result[$code] = $strings["language.name"]; + } + }catch(LanguageNotFoundException $e){ + // no-op } } @@ -124,7 +129,10 @@ class Language{ protected static function loadLang(string $path, string $languageCode) : array{ $file = Path::join($path, $languageCode . ".ini"); if(file_exists($file)){ - return array_map('\stripcslashes', Utils::assumeNotFalse(parse_ini_file($file, false, INI_SCANNER_RAW), "Missing or inaccessible required resource files")); + $strings = array_map('stripcslashes', Utils::assumeNotFalse(parse_ini_file($file, false, INI_SCANNER_RAW), "Missing or inaccessible required resource files")); + if(count($strings) > 0){ + return $strings; + } } throw new LanguageNotFoundException("Language \"$languageCode\" not found"); diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index b831ef486..cb8c73eb3 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -345,8 +345,10 @@ class InventoryManager{ public function onClientRemoveWindow(int $id) : void{ if($id === $this->lastInventoryNetworkId){ - $this->remove($id); - $this->player->removeCurrentWindow(); + if(isset($this->windowMap[$id]) && $id !== $this->pendingCloseWindowId){ + $this->remove($id); + $this->player->removeCurrentWindow(); + } }else{ $this->session->getLogger()->debug("Attempted to close inventory with network ID $id, but current is $this->lastInventoryNetworkId"); } diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 6f9286920..3b868e0f5 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -56,6 +56,7 @@ use pocketmine\network\mcpe\handler\LoginPacketHandler; use pocketmine\network\mcpe\handler\PacketHandler; use pocketmine\network\mcpe\handler\PreSpawnPacketHandler; use pocketmine\network\mcpe\handler\ResourcePacksPacketHandler; +use pocketmine\network\mcpe\handler\SessionStartPacketHandler; use pocketmine\network\mcpe\handler\SpawnResponsePacketHandler; use pocketmine\network\mcpe\protocol\AvailableCommandsPacket; use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket; @@ -87,6 +88,7 @@ use pocketmine\network\mcpe\protocol\SetTimePacket; use pocketmine\network\mcpe\protocol\SetTitlePacket; use pocketmine\network\mcpe\protocol\TakeItemActorPacket; use pocketmine\network\mcpe\protocol\TextPacket; +use pocketmine\network\mcpe\protocol\ToastRequestPacket; use pocketmine\network\mcpe\protocol\TransferPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\network\mcpe\protocol\types\command\CommandData; @@ -164,6 +166,7 @@ class NetworkSession{ */ private \SplQueue $compressedQueue; private bool $forceAsyncCompression = true; + private bool $enableCompression = false; //disabled until handshake completed private PacketSerializerContext $packetSerializerContext; @@ -196,17 +199,10 @@ class NetworkSession{ $this->connectTime = time(); - $this->setHandler(new LoginPacketHandler( + $this->setHandler(new SessionStartPacketHandler( $this->server, $this, - function(PlayerInfo $info) : void{ - $this->info = $info; - $this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET); - $this->logger->setPrefix($this->getLogPrefix()); - }, - function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{ - $this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey); - } + fn() => $this->onSessionStartSuccess() )); $this->manager->add($this); @@ -221,6 +217,24 @@ class NetworkSession{ return $this->logger; } + private function onSessionStartSuccess() : void{ + $this->logger->debug("Session start handshake completed, awaiting login packet"); + $this->flushSendBuffer(true); + $this->enableCompression = true; + $this->setHandler(new LoginPacketHandler( + $this->server, + $this, + function(PlayerInfo $info) : void{ + $this->info = $info; + $this->logger->info("Player: " . TextFormat::AQUA . $info->getUsername() . TextFormat::RESET); + $this->logger->setPrefix($this->getLogPrefix()); + }, + function(bool $isAuthenticated, bool $authRequired, ?string $error, ?string $clientPubKey) : void{ + $this->setAuthenticationStatus($isAuthenticated, $authRequired, $error, $clientPubKey); + } + )); + } + protected function createPlayer() : void{ $this->server->createPlayer($this, $this->info, $this->authenticated, $this->cachedOfflinePlayerData)->onCompletion( \Closure::fromCallable([$this, 'onPlayerCreated']), @@ -335,18 +349,22 @@ class NetworkSession{ } } - Timings::$playerNetworkReceiveDecompress->startTiming(); - try{ - $stream = new PacketBatch($this->compressor->decompress($payload)); - }catch(DecompressionException $e){ - $this->logger->debug("Failed to decompress packet: " . base64_encode($payload)); - throw PacketHandlingException::wrap($e, "Compressed packet batch decode error"); - }finally{ - Timings::$playerNetworkReceiveDecompress->stopTiming(); + if($this->enableCompression){ + Timings::$playerNetworkReceiveDecompress->startTiming(); + try{ + $decompressed = $this->compressor->decompress($payload); + }catch(DecompressionException $e){ + $this->logger->debug("Failed to decompress packet: " . base64_encode($payload)); + throw PacketHandlingException::wrap($e, "Compressed packet batch decode error"); + }finally{ + Timings::$playerNetworkReceiveDecompress->stopTiming(); + } + }else{ + $decompressed = $payload; } try{ - foreach($stream->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){ + foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 500) as [$packet, $buffer]){ if($packet === null){ $this->logger->debug("Unknown packet: " . base64_encode($buffer)); throw new PacketHandlingException("Unknown packet received"); @@ -451,7 +469,14 @@ class NetworkSession{ }elseif($this->forceAsyncCompression){ $syncMode = false; } - $promise = $this->server->prepareBatch(PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer), $this->compressor, $syncMode); + + $batch = PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer); + if($this->enableCompression){ + $promise = $this->server->prepareBatch($batch, $this->compressor, $syncMode); + }else{ + $promise = new CompressBatchPromise(); + $promise->resolve($batch->getBuffer()); + } $this->sendBuffer = []; $this->queueCompressedNoBufferFlush($promise, $immediate); } @@ -1076,6 +1101,10 @@ class NetworkSession{ $this->sendDataPacket(EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER)); } + public function onToastNotification(string $title, string $body) : void{ + $this->sendDataPacket(ToastRequestPacket::create($title, $body)); + } + public function tick() : void{ if($this->info === null){ if(time() >= $this->connectTime + 10){ diff --git a/src/network/mcpe/cache/ChunkCache.php b/src/network/mcpe/cache/ChunkCache.php index dae5e27d6..15b696945 100644 --- a/src/network/mcpe/cache/ChunkCache.php +++ b/src/network/mcpe/cache/ChunkCache.php @@ -43,8 +43,6 @@ class ChunkCache implements ChunkListener{ /** * Fetches the ChunkCache instance for the given world. This lazily creates cache systems as needed. - * - * @return ChunkCache */ public static function getInstance(World $world, Compressor $compressor) : self{ $worldId = spl_object_id($world); diff --git a/src/network/mcpe/cache/StaticPacketCache.php b/src/network/mcpe/cache/StaticPacketCache.php index b4e1a7150..c3eca501d 100644 --- a/src/network/mcpe/cache/StaticPacketCache.php +++ b/src/network/mcpe/cache/StaticPacketCache.php @@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket; use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; use pocketmine\network\mcpe\protocol\types\CacheableNbt; use pocketmine\utils\SingletonTrait; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function file_get_contents; class StaticPacketCache{ diff --git a/src/network/mcpe/convert/GlobalItemTypeDictionary.php b/src/network/mcpe/convert/GlobalItemTypeDictionary.php index 6940a91d7..f33683346 100644 --- a/src/network/mcpe/convert/GlobalItemTypeDictionary.php +++ b/src/network/mcpe/convert/GlobalItemTypeDictionary.php @@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\types\ItemTypeEntry; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\SingletonTrait; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function file_get_contents; use function is_array; use function is_bool; diff --git a/src/network/mcpe/convert/ItemTranslator.php b/src/network/mcpe/convert/ItemTranslator.php index 4a2036e0b..30e3d6b92 100644 --- a/src/network/mcpe/convert/ItemTranslator.php +++ b/src/network/mcpe/convert/ItemTranslator.php @@ -28,7 +28,7 @@ use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\SingletonTrait; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function array_key_exists; use function file_get_contents; use function is_array; diff --git a/src/network/mcpe/convert/RuntimeBlockMapping.php b/src/network/mcpe/convert/RuntimeBlockMapping.php index 8a47de971..547bd0d81 100644 --- a/src/network/mcpe/convert/RuntimeBlockMapping.php +++ b/src/network/mcpe/convert/RuntimeBlockMapping.php @@ -32,7 +32,7 @@ use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\utils\SingletonTrait; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function file_get_contents; /** diff --git a/src/network/mcpe/convert/TypeConverter.php b/src/network/mcpe/convert/TypeConverter.php index 5ed60745c..b1f5f1f38 100644 --- a/src/network/mcpe/convert/TypeConverter.php +++ b/src/network/mcpe/convert/TypeConverter.php @@ -43,7 +43,9 @@ use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction; use pocketmine\network\mcpe\protocol\types\inventory\UIInventorySlotOffset; +use pocketmine\network\mcpe\protocol\types\recipe\IntIdMetaItemDescriptor; use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient; +use pocketmine\network\mcpe\protocol\types\recipe\StringIdMetaItemDescriptor; use pocketmine\player\GameMode; use pocketmine\player\Player; use pocketmine\utils\AssumptionFailedError; @@ -110,7 +112,7 @@ class TypeConverter{ public function coreItemStackToRecipeIngredient(Item $itemStack) : RecipeIngredient{ if($itemStack->isNull()){ - return new RecipeIngredient(0, 0, 0); + return new RecipeIngredient(null, 0); } if($itemStack->hasAnyDamageValue()){ [$id, ] = ItemTranslator::getInstance()->toNetworkId($itemStack->getId(), 0); @@ -118,15 +120,25 @@ class TypeConverter{ }else{ [$id, $meta] = ItemTranslator::getInstance()->toNetworkId($itemStack->getId(), $itemStack->getMeta()); } - return new RecipeIngredient($id, $meta, $itemStack->getCount()); + return new RecipeIngredient(new IntIdMetaItemDescriptor($id, $meta), $itemStack->getCount()); } public function recipeIngredientToCoreItemStack(RecipeIngredient $ingredient) : Item{ - if($ingredient->getId() === 0){ + $descriptor = $ingredient->getDescriptor(); + if($descriptor === null){ return VanillaItems::AIR(); } - [$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($ingredient->getId(), $ingredient->getMeta()); - return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount()); + if($descriptor instanceof IntIdMetaItemDescriptor){ + [$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($descriptor->getId(), $descriptor->getMeta()); + return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount()); + } + if($descriptor instanceof StringIdMetaItemDescriptor){ + $intId = GlobalItemTypeDictionary::getInstance()->getDictionary()->fromStringId($descriptor->getId()); + [$id, $meta] = ItemTranslator::getInstance()->fromNetworkIdWithWildcardHandling($intId, $descriptor->getMeta()); + return ItemFactory::getInstance()->get($id, $meta, $ingredient->getCount()); + } + + throw new \LogicException("Unsupported conversion of recipe ingredient to core item stack"); } public function coreItemStackToNet(Item $itemStack) : ItemStack{ diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 252eddb2e..ef8159bee 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -51,7 +51,6 @@ use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\ActorEventPacket; use pocketmine\network\mcpe\protocol\ActorPickRequestPacket; -use pocketmine\network\mcpe\protocol\AdventureSettingsPacket; use pocketmine\network\mcpe\protocol\AnimatePacket; use pocketmine\network\mcpe\protocol\BlockActorDataPacket; use pocketmine\network\mcpe\protocol\BlockPickRequestPacket; @@ -687,10 +686,6 @@ class InGamePacketHandler extends PacketHandler{ return true; //this is a broken useless packet, so we don't use it } - public function handleAdventureSettings(AdventureSettingsPacket $packet) : bool{ - return true; //no longer used, but the client still sends it for flight changes - } - public function handleBlockActorData(BlockActorDataPacket $packet) : bool{ $pos = new Vector3($packet->blockPosition->getX(), $packet->blockPosition->getY(), $packet->blockPosition->getZ()); if($pos->distanceSquared($this->player->getLocation()) > 10000){ diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index cf7c9b0f3..a451f59a9 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -30,6 +30,7 @@ use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\InventoryManager; use pocketmine\network\mcpe\NetworkSession; +use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; use pocketmine\network\mcpe\protocol\StartGamePacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; @@ -62,6 +63,7 @@ class PreSpawnPacketHandler extends PacketHandler{ $location = $this->player->getLocation(); $world = $location->getWorld(); + $this->session->getLogger()->debug("Preparing StartGamePacket"); $levelSettings = new LevelSettings(); $levelSettings->seed = -1; $levelSettings->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly @@ -105,22 +107,41 @@ class PreSpawnPacketHandler extends PacketHandler{ GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(), )); + $this->session->getLogger()->debug("Sending actor identifiers"); $this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers()); + + $this->session->getLogger()->debug("Sending biome definitions"); $this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs()); + + $this->session->getLogger()->debug("Sending attributes"); $this->session->syncAttributes($this->player, $this->player->getAttributeMap()->getAll()); + + $this->session->getLogger()->debug("Sending available commands"); $this->session->syncAvailableCommands(); + + $this->session->getLogger()->debug("Sending abilities"); $this->session->syncAbilities($this->player); $this->session->syncAdventureSettings(); + + $this->session->getLogger()->debug("Sending effects"); foreach($this->player->getEffects()->all() as $effect){ $this->session->onEntityEffectAdded($this->player, $effect, false); } + + $this->session->getLogger()->debug("Sending actor metadata"); $this->player->sendData([$this->player]); + $this->session->getLogger()->debug("Sending inventory"); $this->inventoryManager->syncAll(); - $this->inventoryManager->syncCreative(); $this->inventoryManager->syncSelectedHotbarSlot(); + + $this->session->getLogger()->debug("Sending creative inventory data"); + $this->inventoryManager->syncCreative(); + + $this->session->getLogger()->debug("Sending crafting data"); $this->session->sendDataPacket(CraftingDataCache::getInstance()->getCache($this->server->getCraftingManager())); + $this->session->getLogger()->debug("Sending player list"); $this->session->syncPlayerList($this->server->getOnlinePlayers()); } @@ -129,4 +150,10 @@ class PreSpawnPacketHandler extends PacketHandler{ return true; } + + public function handlePlayerAuthInput(PlayerAuthInputPacket $packet) : bool{ + //the client will send this every tick once we start sending chunks, but we don't handle it in this stage + //this is very spammy so we filter it out + return true; + } } diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php index a7c603ceb..d1ba85724 100644 --- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php +++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php @@ -65,9 +65,19 @@ class ResourcePacksPacketHandler extends PacketHandler{ ){} public function setUp() : void{ - $resourcePackEntries = array_map(static function(ResourcePack $pack) : ResourcePackInfoEntry{ + $resourcePackEntries = array_map(function(ResourcePack $pack) : ResourcePackInfoEntry{ //TODO: more stuff - return new ResourcePackInfoEntry($pack->getPackId(), $pack->getPackVersion(), $pack->getPackSize(), "", "", "", false); + $encryptionKey = $this->resourcePackManager->getPackEncryptionKey($pack->getPackId()); + + return new ResourcePackInfoEntry( + $pack->getPackId(), + $pack->getPackVersion(), + $pack->getPackSize(), + $encryptionKey ?? "", + "", + $pack->getPackId(), + false + ); }, $this->resourcePackManager->getResourceStack()); //TODO: support forcing server packs $this->session->sendDataPacket(ResourcePacksInfoPacket::create($resourcePackEntries, [], $this->resourcePackManager->resourcePacksRequired(), false, false)); diff --git a/src/network/mcpe/handler/SessionStartPacketHandler.php b/src/network/mcpe/handler/SessionStartPacketHandler.php new file mode 100644 index 000000000..687422de2 --- /dev/null +++ b/src/network/mcpe/handler/SessionStartPacketHandler.php @@ -0,0 +1,76 @@ +getProtocolVersion(); + if(!$this->isCompatibleProtocol($protocolVersion)){ + $this->session->sendDataPacket(PlayStatusPacket::create($protocolVersion < ProtocolInfo::CURRENT_PROTOCOL ? PlayStatusPacket::LOGIN_FAILED_CLIENT : PlayStatusPacket::LOGIN_FAILED_SERVER), true); + + //This pocketmine disconnect message will only be seen by the console (PlayStatusPacket causes the messages to be shown for the client) + $this->session->disconnect( + $this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_disconnect_incompatibleProtocol((string) $protocolVersion)), + false + ); + + return true; + } + + //TODO: we're filling in the defaults to get pre-1.19.30 behaviour back for now, but we should explore the new options in the future + $this->session->sendDataPacket(NetworkSettingsPacket::create( + NetworkSettingsPacket::COMPRESS_EVERYTHING, + CompressionAlgorithm::ZLIB, + false, + 0, + 0 + )); + ($this->onSuccess)(); + + return true; + } + + protected function isCompatibleProtocol(int $protocolVersion) : bool{ + return $protocolVersion === ProtocolInfo::CURRENT_PROTOCOL; + } +} diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php index ddb221692..33a50ac56 100644 --- a/src/network/mcpe/raklib/RakLibInterface.php +++ b/src/network/mcpe/raklib/RakLibInterface.php @@ -58,7 +58,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ * Sometimes this gets changed when the MCPE-layer protocol gets broken to the point where old and new can't * communicate. It's important that we check this to avoid catastrophes. */ - private const MCPE_RAKNET_PROTOCOL_VERSION = 10; + private const MCPE_RAKNET_PROTOCOL_VERSION = 11; private const MCPE_RAKNET_PACKET_ID = "\xfe"; diff --git a/src/network/mcpe/raklib/RakLibThreadCrashInfo.php b/src/network/mcpe/raklib/RakLibThreadCrashInfo.php index 957afdacd..60e04b4b4 100644 --- a/src/network/mcpe/raklib/RakLibThreadCrashInfo.php +++ b/src/network/mcpe/raklib/RakLibThreadCrashInfo.php @@ -47,7 +47,6 @@ final class RakLibThreadCrashInfo{ return new self(null, $info["message"], $info["file"], $info["line"]); } - /** @return string|null */ public function getClass() : ?string{ return $this->class; } public function getMessage() : string{ return $this->message; } diff --git a/src/player/Player.php b/src/player/Player.php index 76ff30659..157038beb 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -418,6 +418,15 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return $this->lastPlayed - $this->firstPlayed > 1; // microtime(true) - microtime(true) may have less than one millisecond difference } + /** + * Sets whether the player is allowed to toggle flight mode. + * + * If set to false, the player will be locked in its current flight mode (flying/not flying), and attempts by the + * player to enter or exit flight mode will be prevented. + * + * Note: Setting this to false DOES NOT change whether the player is currently flying. Use + * {@link Player::setFlying()} for that purpose. + */ public function setAllowFlight(bool $value) : void{ if($this->allowFlight !== $value){ $this->allowFlight = $value; @@ -425,10 +434,24 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ } } + /** + * Returns whether the player is allowed to toggle its flight state. + * + * If false, the player is locked in its current flight mode (flying/not flying), and attempts by the player to + * enter or exit flight mode will be prevented. + */ public function getAllowFlight() : bool{ return $this->allowFlight; } + /** + * Sets whether the player's movement may be obstructed by blocks with collision boxes. + * If set to false, the player can move through any block unobstructed. + * + * Note: Enabling flight mode in conjunction with this is recommended. A non-flying player will simply fall through + * the ground into the void. + * @see Player::setFlying() + */ public function setHasBlockCollision(bool $value) : void{ if($this->blockCollision !== $value){ $this->blockCollision = $value; @@ -436,6 +459,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ } } + /** + * Returns whether blocks may obstruct the player's movement. + * If false, the player can move through any block unobstructed. + */ public function hasBlockCollision() : bool{ return $this->blockCollision; } @@ -1021,7 +1048,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ protected function internalSetGameMode(GameMode $gameMode) : void{ $this->gamemode = $gameMode; - $this->allowFlight = $this->isCreative(); + $this->allowFlight = $this->gamemode->equals(GameMode::CREATIVE()); $this->hungerManager->setEnabled($this->isSurvival()); if($this->isSpectator()){ @@ -1316,6 +1343,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->lastUpdate = $currentTick; + if($this->justCreated){ + $this->onFirstUpdate($currentTick); + } + if(!$this->isAlive() && $this->spawned){ $this->onDeathUpdate($tickDiff); return true; @@ -1986,6 +2017,13 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->getNetworkSession()->onTip($message); } + /** + * Sends a toast message to the player, or queue to send it if a toast message is already shown. + */ + public function sendToastNotification(string $title, string $body) : void{ + $this->getNetworkSession()->onToastNotification($title, $body); + } + /** * Sends a Form to the player, or queue to send it if a form is already open. * diff --git a/src/plugin/PluginBase.php b/src/plugin/PluginBase.php index e9bdc2f20..9b0ac2399 100644 --- a/src/plugin/PluginBase.php +++ b/src/plugin/PluginBase.php @@ -33,7 +33,7 @@ use pocketmine\Server; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Config; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function count; use function dirname; use function fclose; diff --git a/src/plugin/PluginManager.php b/src/plugin/PluginManager.php index acffae53e..fc16fa7ea 100644 --- a/src/plugin/PluginManager.php +++ b/src/plugin/PluginManager.php @@ -40,7 +40,7 @@ use pocketmine\Server; use pocketmine\timings\TimingsHandler; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Utils; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function array_diff_key; use function array_key_exists; use function array_keys; @@ -75,6 +75,9 @@ class PluginManager{ /** @var Plugin[] */ protected $enabledPlugins = []; + /** @var array> */ + private array $pluginDependents = []; + private bool $loadPluginsGuard = false; /** @@ -453,6 +456,15 @@ class PluginManager{ if($plugin->isEnabled()){ //the plugin may have disabled itself during onEnable() $this->enabledPlugins[$plugin->getDescription()->getName()] = $plugin; + foreach($plugin->getDescription()->getDepend() as $dependency){ + $this->pluginDependents[$dependency][$plugin->getDescription()->getName()] = true; + } + foreach($plugin->getDescription()->getSoftDepend() as $dependency){ + if(isset($this->plugins[$dependency])){ + $this->pluginDependents[$dependency][$plugin->getDescription()->getName()] = true; + } + } + (new PluginEnableEvent($plugin))->call(); return true; @@ -472,8 +484,19 @@ class PluginManager{ } public function disablePlugins() : void{ - foreach($this->getPlugins() as $plugin){ - $this->disablePlugin($plugin); + while(count($this->enabledPlugins) > 0){ + foreach($this->enabledPlugins as $plugin){ + if(!$plugin->isEnabled()){ + continue; //in case a plugin disabled another plugin + } + $name = $plugin->getDescription()->getName(); + if(isset($this->pluginDependents[$name]) && count($this->pluginDependents[$name]) > 0){ + $this->server->getLogger()->debug("Deferring disable of plugin $name due to dependent plugins still enabled: " . implode(", ", array_keys($this->pluginDependents[$name]))); + continue; + } + + $this->disablePlugin($plugin); + } } } @@ -483,6 +506,12 @@ class PluginManager{ (new PluginDisableEvent($plugin))->call(); unset($this->enabledPlugins[$plugin->getDescription()->getName()]); + foreach(Utils::stringifyKeys($this->pluginDependents) as $dependency => $dependentList){ + unset($this->pluginDependents[$dependency][$plugin->getDescription()->getName()]); + if(count($this->pluginDependents[$dependency]) === 0){ + unset($this->pluginDependents[$dependency]); + } + } $plugin->onEnableStateChange(false); $plugin->getScheduler()->shutdown(); diff --git a/src/resourcepacks/ResourcePackManager.php b/src/resourcepacks/ResourcePackManager.php index 4aaa9afc7..d1482d8b6 100644 --- a/src/resourcepacks/ResourcePackManager.php +++ b/src/resourcepacks/ResourcePackManager.php @@ -23,12 +23,14 @@ declare(strict_types=1); namespace pocketmine\resourcepacks; +use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\utils\Config; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function array_keys; use function copy; use function count; use function file_exists; +use function file_get_contents; use function gettype; use function is_array; use function is_dir; @@ -49,6 +51,12 @@ class ResourcePackManager{ /** @var ResourcePack[] */ private array $uuidList = []; + /** + * @var string[] + * @phpstan-var array + */ + private array $encryptionKeys = []; + /** * @param string $path Path to resource-packs directory. */ @@ -105,7 +113,19 @@ class ResourcePackManager{ if($newPack instanceof ResourcePack){ $this->resourcePacks[] = $newPack; - $this->uuidList[strtolower($newPack->getPackId())] = $newPack; + $index = strtolower($newPack->getPackId()); + $this->uuidList[$index] = $newPack; + + $keyPath = Path::join($this->path, $pack . ".key"); + if(file_exists($keyPath)){ + try{ + $this->encryptionKeys[$index] = ErrorToExceptionHandler::trapAndRemoveFalse( + fn() => file_get_contents($keyPath) + ); + }catch(\ErrorException $e){ + throw new ResourcePackException("Could not read encryption key file: " . $e->getMessage(), 0, $e); + } + } }else{ throw new ResourcePackException("Format not recognized"); } @@ -153,4 +173,11 @@ class ResourcePackManager{ public function getPackIdList() : array{ return array_keys($this->uuidList); } + + /** + * Returns the key with which the pack was encrypted, or null if the pack has no key. + */ + public function getPackEncryptionKey(string $id) : ?string{ + return $this->encryptionKeys[strtolower($id)] ?? null; + } } diff --git a/src/scheduler/DumpWorkerMemoryTask.php b/src/scheduler/DumpWorkerMemoryTask.php index 98b5e8909..b1cf3840c 100644 --- a/src/scheduler/DumpWorkerMemoryTask.php +++ b/src/scheduler/DumpWorkerMemoryTask.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\scheduler; use pocketmine\MemoryManager; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; /** * Task used to dump memory from AsyncWorkers diff --git a/src/scheduler/TaskHandler.php b/src/scheduler/TaskHandler.php index e1cadb652..50d9f01b0 100644 --- a/src/scheduler/TaskHandler.php +++ b/src/scheduler/TaskHandler.php @@ -69,6 +69,9 @@ class TaskHandler{ return $this->nextRun; } + /** + * @internal + */ public function setNextRun(int $ticks) : void{ $this->nextRun = $ticks; } @@ -103,11 +106,17 @@ class TaskHandler{ } } + /** + * @internal + */ public function remove() : void{ $this->cancelled = true; $this->task->setHandler(null); } + /** + * @internal + */ public function run() : void{ $this->timings->startTiming(); try{ diff --git a/src/thread/Worker.php b/src/thread/Worker.php index 1315202b1..572da3c67 100644 --- a/src/thread/Worker.php +++ b/src/thread/Worker.php @@ -56,7 +56,9 @@ abstract class Worker extends \Worker{ $this->isKilled = true; if(!$this->isShutdown()){ - while($this->unstack() !== null); + $this->synchronized(function() : void{ + while($this->unstack() !== null); + }); $this->notify(); $this->shutdown(); } diff --git a/src/utils/BroadcastLoggerForwarder.php b/src/utils/BroadcastLoggerForwarder.php new file mode 100644 index 000000000..a015615b5 --- /dev/null +++ b/src/utils/BroadcastLoggerForwarder.php @@ -0,0 +1,79 @@ +perm = new PermissibleBase([]); + } + + public function getLanguage() : Language{ + return $this->language; + } + + public function sendMessage(Translatable|string $message) : void{ + if($message instanceof Translatable){ + $this->logger->info($this->language->translate($message)); + }else{ + $this->logger->info($message); + } + } + + public function getServer() : Server{ + return $this->server; + } + + public function getName() : string{ + return "Broadcast Logger Forwarder"; + } + + public function getScreenLineHeight() : int{ + return PHP_INT_MAX; + } + + public function setScreenLineHeight(?int $height) : void{ + //NOOP + } +} diff --git a/src/utils/Config.php b/src/utils/Config.php index ffc2db69a..306f9eb80 100644 --- a/src/utils/Config.php +++ b/src/utils/Config.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\utils; use pocketmine\errorhandler\ErrorToExceptionHandler; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function array_change_key_case; use function array_fill_keys; use function array_keys; diff --git a/src/utils/EnumTrait.php b/src/utils/EnumTrait.php index 315561444..c9d24e0fc 100644 --- a/src/utils/EnumTrait.php +++ b/src/utils/EnumTrait.php @@ -48,6 +48,7 @@ trait EnumTrait{ * This is overridden to change the return typehint. * * @return self[] + * @phpstan-return array */ public static function getAll() : array{ //phpstan doesn't support generic traits yet :( diff --git a/src/utils/Filesystem.php b/src/utils/Filesystem.php index 20ec8b312..a4c1ff236 100644 --- a/src/utils/Filesystem.php +++ b/src/utils/Filesystem.php @@ -24,7 +24,7 @@ declare(strict_types=1); namespace pocketmine\utils; use pocketmine\errorhandler\ErrorToExceptionHandler; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function copy; use function dirname; use function fclose; diff --git a/src/utils/MainLogger.php b/src/utils/MainLogger.php index 6cefce218..139388af7 100644 --- a/src/utils/MainLogger.php +++ b/src/utils/MainLogger.php @@ -191,7 +191,7 @@ class MainLogger extends \AttachableThreadedLogger implements \BufferedLogger{ $threadName = (new \ReflectionClass($thread))->getShortName() . " thread"; } - $message = sprintf($this->format, $time->format("H:i:s.v"), $color, $threadName, $prefix, TextFormat::clean($message, false)); + $message = sprintf($this->format, $time->format("H:i:s.v"), $color, $threadName, $prefix, TextFormat::addBase($color, TextFormat::clean($message, false))); if(!Terminal::isInit()){ Terminal::init($this->useFormattingCodes); //lazy-init colour codes because we don't know if they've been registered on this thread diff --git a/src/utils/RegistryTrait.php b/src/utils/RegistryTrait.php index 989800ef0..776980599 100644 --- a/src/utils/RegistryTrait.php +++ b/src/utils/RegistryTrait.php @@ -29,7 +29,10 @@ use function mb_strtoupper; use function preg_match; trait RegistryTrait{ - /** @var object[] */ + /** + * @var object[] + * @phpstan-var array + */ private static $members = null; private static function verifyName(string $name) : void{ @@ -107,6 +110,7 @@ trait RegistryTrait{ /** * @return object[] + * @phpstan-return array */ private static function _registryGetAll() : array{ self::checkInit(); diff --git a/src/utils/TextFormat.php b/src/utils/TextFormat.php index d2250057f..dfd6a359a 100644 --- a/src/utils/TextFormat.php +++ b/src/utils/TextFormat.php @@ -158,6 +158,31 @@ abstract class TextFormat{ return self::preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-gk-or])/u', TextFormat::ESCAPE . '$1', $string); } + /** + * Adds base formatting to the string. The given format codes will be inserted directly after any RESET (§r) codes. + * + * This is useful for log messages, where a RESET code should return to the log message's original colour (e.g. + * blue for NOTICE), rather than whatever the terminal's base text colour is (usually some off-white colour). + * + * Example behaviour: + * - Base format "§c" (red) + "Hello" (no format) = "§r§cHello" + * - Base format "§c" + "Hello §rWorld" = "§r§cHello §r§cWorld" + * + * Note: Adding base formatting to the output string a second time will result in a combination of formats from both + * calls. This is not by design, but simply a consequence of the way the function is implemented. + */ + public static function addBase(string $baseFormat, string $string) : string{ + $baseFormatParts = self::tokenize($baseFormat); + foreach($baseFormatParts as $part){ + if(!isset(self::FORMATS[$part]) && !isset(self::COLORS[$part])){ + throw new \InvalidArgumentException("Unexpected base format token \"$part\", expected only color and format tokens"); + } + } + $baseFormat = self::RESET . $baseFormat; + + return $baseFormat . str_replace(TextFormat::RESET, $baseFormat, $string); + } + /** * Returns an HTML-formatted string with colors/markup */ diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 03377b6c4..35d6595ca 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -69,6 +69,7 @@ use function mb_check_encoding; use function ob_end_clean; use function ob_get_contents; use function ob_start; +use function opcache_get_status; use function ord; use function php_uname; use function phpversion; @@ -108,6 +109,7 @@ final class Utils{ private static ?string $os = null; private static ?UuidInterface $serverUniqueId = null; + private static ?int $cpuCores = null; /** * Returns a readable identifier for the given Closure, including file and line. @@ -295,14 +297,11 @@ final class Utils{ } public static function getCoreCount(bool $recalculate = false) : int{ - static $processors = 0; - - if($processors > 0 && !$recalculate){ - return $processors; - }else{ - $processors = 0; + if(self::$cpuCores !== null && !$recalculate){ + return self::$cpuCores; } + $processors = 0; switch(Utils::getOS()){ case Utils::OS_LINUX: case Utils::OS_ANDROID: @@ -326,7 +325,7 @@ final class Utils{ $processors = (int) getenv("NUMBER_OF_PROCESSORS"); break; } - return $processors; + return self::$cpuCores = $processors; } /** @@ -365,12 +364,6 @@ final class Utils{ $ord -= 0x100; } $hash = 31 * $hash + $ord; - while($hash > 0x7FFFFFFF){ - $hash -= 0x100000000; - } - while($hash < -0x80000000){ - $hash += 0x100000000; - } $hash &= 0xFFFFFFFF; } return $hash; @@ -486,8 +479,8 @@ final class Utils{ */ public static function currentTrace(int $skipFrames = 0) : array{ ++$skipFrames; //omit this frame from trace, in addition to other skipped frames - if(function_exists("xdebug_get_function_stack")){ - $trace = array_reverse(xdebug_get_function_stack()); + if(function_exists("xdebug_get_function_stack") && count($trace = @xdebug_get_function_stack()) !== 0){ + $trace = array_reverse($trace); }else{ $e = new \Exception(); $trace = $e->getTrace(); @@ -634,4 +627,30 @@ final class Utils{ public static function checkLocationNotInfOrNaN(Location $location) : void{ self::checkVector3NotInfOrNaN($location); } + + /** + * Returns an integer describing the current OPcache JIT setting. + * @see https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit + */ + public static function getOpcacheJitMode() : ?int{ + if( + function_exists('opcache_get_status') && + ($opcacheStatus = opcache_get_status(false)) !== false && + isset($opcacheStatus["jit"]["on"]) + ){ + $jit = $opcacheStatus["jit"]; + if($jit["on"] === true){ + return (($jit["opt_flags"] >> 2) * 1000) + + (($jit["opt_flags"] & 0x03) * 100) + + ($jit["kind"] * 10) + + $jit["opt_level"]; + } + + //jit available, but disabled + return 0; + } + + //jit not available + return null; + } } diff --git a/src/wizard/SetupWizard.php b/src/wizard/SetupWizard.php index 8373b0e8b..c9170bd8d 100644 --- a/src/wizard/SetupWizard.php +++ b/src/wizard/SetupWizard.php @@ -39,7 +39,7 @@ use pocketmine\utils\Internet; use pocketmine\utils\InternetException; use pocketmine\utils\Utils; use pocketmine\VersionInfo; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function fgets; use function sleep; use function strtolower; diff --git a/src/world/Explosion.php b/src/world/Explosion.php index 9f117245c..5ccdc0faa 100644 --- a/src/world/Explosion.php +++ b/src/world/Explosion.php @@ -195,7 +195,7 @@ class Explosion{ } $entity->attack($ev); - $entity->setMotion($motion->multiply($impact)); + $entity->setMotion($entity->getMotion()->addVector($motion->multiply($impact))); } } diff --git a/src/world/World.php b/src/world/World.php index 13dc24d1e..0071f221f 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -129,6 +129,11 @@ use const PHP_INT_MIN; #include +/** + * @phpstan-type ChunkPosHash int + * @phpstan-type BlockPosHash int + * @phpstan-type ChunkBlockPosHash int + */ class World implements ChunkManager{ private static int $worldIdCounter = 1; @@ -152,26 +157,40 @@ class World implements ChunkManager{ public const DEFAULT_TICKED_BLOCKS_PER_SUBCHUNK_PER_TICK = 3; - /** @var Player[] */ + /** + * @var Player[] entity runtime ID => Player + * @phpstan-var array + */ private array $players = []; - /** @var Entity[] */ + /** + * @var Entity[] entity runtime ID => Entity + * @phpstan-var array + */ private array $entities = []; /** - * @var Vector3[] + * @var Vector3[] entity runtime ID => Vector3 * @phpstan-var array */ private array $entityLastKnownPositions = []; /** - * @var Entity[][] - * @phpstan-var array> + * @var Entity[][] chunkHash => [entity runtime ID => Entity] + * @phpstan-var array> */ private array $entitiesByChunk = []; - /** @var Entity[] */ + /** + * @var Entity[] entity runtime ID => Entity + * @phpstan-var array + */ public $updateEntities = []; - /** @var Block[][] */ + + private bool $inDynamicStateRecalculation = false; + /** + * @var Block[][] chunkHash => [relativeBlockHash => Block] + * @phpstan-var array> + */ private array $blockCache = []; private int $sendTimeTicker = 0; @@ -183,22 +202,43 @@ class World implements ChunkManager{ private int $minY; private int $maxY; - /** @var TickingChunkLoader[] */ + /** + * @var TickingChunkLoader[] spl_object_id => TickingChunkLoader + * @phpstan-var array + */ private array $tickingLoaders = []; - /** @var int[] */ + /** + * @var int[] spl_object_id => number of chunks + * @phpstan-var array + */ private array $tickingLoaderCounter = []; - /** @var ChunkLoader[][] */ + /** + * @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader] + * @phpstan-var array> + */ private array $chunkLoaders = []; - /** @var ChunkListener[][] */ + /** + * @var ChunkListener[][] chunkHash => [spl_object_id => ChunkListener] + * @phpstan-var array> + */ private array $chunkListeners = []; - /** @var Player[][] */ + /** + * @var Player[][] chunkHash => [spl_object_id => Player] + * @phpstan-var array> + */ private array $playerChunkListeners = []; - /** @var ClientboundPacket[][] */ + /** + * @var ClientboundPacket[][] + * @phpstan-var array> + */ private array $packetBuffersByChunk = []; - /** @var float[] */ + /** + * @var float[] chunkHash => timestamp of request + * @phpstan-var array + */ private array $unloadQueue = []; private int $time; @@ -211,44 +251,65 @@ class World implements ChunkManager{ private string $folderName; private string $displayName; - /** @var Chunk[] */ + /** + * @var Chunk[] + * @phpstan-var array + */ private array $chunks = []; - /** @var Vector3[][] */ + /** + * @var Vector3[][] chunkHash => [relativeBlockHash => Vector3] + * @phpstan-var array> + */ private array $changedBlocks = []; /** @phpstan-var ReversePriorityQueue */ private ReversePriorityQueue $scheduledBlockUpdateQueue; - /** @var int[] */ + /** + * @var int[] blockHash => tick delay + * @phpstan-var array + */ private array $scheduledBlockUpdateQueueIndex = []; /** @phpstan-var \SplQueue */ private \SplQueue $neighbourBlockUpdateQueue; - /** @var bool[] blockhash => dummy */ + /** + * @var true[] blockhash => dummy + * @phpstan-var array + */ private array $neighbourBlockUpdateQueueIndex = []; - /** @var bool[] */ + /** + * @var bool[] chunkHash => isValid + * @phpstan-var array + */ private array $activeChunkPopulationTasks = []; - /** @var ChunkLockId[] */ + /** + * @var ChunkLockId[] + * @phpstan-var array + */ private array $chunkLock = []; private int $maxConcurrentChunkPopulationTasks = 2; /** * @var PromiseResolver[] chunkHash => promise - * @phpstan-var array> + * @phpstan-var array> */ private array $chunkPopulationRequestMap = []; /** * @var \SplQueue (queue of chunkHashes) - * @phpstan-var \SplQueue + * @phpstan-var \SplQueue */ private \SplQueue $chunkPopulationRequestQueue; /** * @var true[] chunkHash => dummy - * @phpstan-var array + * @phpstan-var array */ private array $chunkPopulationRequestQueueIndex = []; - /** @var bool[] */ + /** + * @var true[] + * @phpstan-var array + */ private array $generatorRegisteredWorkers = []; private bool $autoSave = true; @@ -258,7 +319,10 @@ class World implements ChunkManager{ private int $chunkTickRadius; private int $chunksPerTick; private int $tickedBlocksPerSubchunkPerTick = self::DEFAULT_TICKED_BLOCKS_PER_SUBCHUNK_PER_TICK; - /** @var bool[] */ + /** + * @var true[] + * @phpstan-var array + */ private array $randomTickBlocks = []; /** @var WorldTimings */ @@ -284,6 +348,9 @@ class World implements ChunkManager{ private \Logger $logger; + /** + * @phpstan-return ChunkPosHash + */ public static function chunkHash(int $x, int $z) : int{ return morton2d_encode($x, $z); } @@ -300,6 +367,9 @@ class World implements ChunkManager{ private const BLOCKHASH_X_SHIFT = self::BLOCKHASH_Y_BITS; private const BLOCKHASH_Z_SHIFT = self::BLOCKHASH_X_SHIFT + self::BLOCKHASH_XZ_EXTRA_BITS; + /** + * @phpstan-return BlockPosHash + */ public static function blockHash(int $x, int $y, int $z) : int{ $shiftedY = $y + self::BLOCKHASH_Y_OFFSET; if(($shiftedY & (~0 << self::BLOCKHASH_Y_BITS)) !== 0){ @@ -324,6 +394,9 @@ class World implements ChunkManager{ return morton3d_encode($x, $y, $z); } + /** + * @phpstan-param BlockPosHash $hash + */ public static function getBlockXYZ(int $hash, ?int &$x, ?int &$y, ?int &$z) : void{ [$baseX, $baseY, $baseZ] = morton3d_decode($hash); @@ -335,6 +408,9 @@ class World implements ChunkManager{ $z = (($baseZ & self::BLOCKHASH_XZ_MASK) | $extraZ) << self::BLOCKHASH_XZ_SIGN_SHIFT >> self::BLOCKHASH_XZ_SIGN_SHIFT; } + /** + * @phpstan-param ChunkPosHash $hash + */ public static function getXZ(int $hash, ?int &$x, ?int &$z) : void{ [$x, $z] = morton2d_decode($hash); } @@ -554,6 +630,28 @@ class World implements ChunkManager{ unset($this->unloadCallbacks[spl_object_id($callback)]); } + /** + * Returns a list of players who are in the given filter and also using the chunk containing the target position. + * Used for broadcasting sounds and particles with specific targets. + * + * @param Player[] $allowed + * @phpstan-param list $allowed + * + * @return array + */ + private function filterViewersForPosition(Vector3 $pos, array $allowed) : array{ + $candidates = $this->getViewersForPosition($pos); + $filtered = []; + foreach($allowed as $player){ + $k = spl_object_id($player); + if(isset($candidates[$k])){ + $filtered[$k] = $candidates[$k]; + } + } + + return $filtered; + } + /** * @param Player[]|null $players */ @@ -565,7 +663,7 @@ class World implements ChunkManager{ $this->broadcastPacketToViewers($pos, $e); } }else{ - $this->server->broadcastPackets($players, $pk); + $this->server->broadcastPackets($this->filterViewersForPosition($pos, $players), $pk); } } } @@ -581,7 +679,7 @@ class World implements ChunkManager{ $this->broadcastPacketToViewers($pos, $e); } }else{ - $this->server->broadcastPackets($players, $pk); + $this->server->broadcastPackets($this->filterViewersForPosition($pos, $players), $pk); } } } @@ -600,7 +698,8 @@ class World implements ChunkManager{ * * Returns a list of players who have the target chunk within their view distance. * - * @return Player[] + * @return Player[] spl_object_id => Player + * @phpstan-return array */ public function getChunkPlayers(int $chunkX, int $chunkZ) : array{ return $this->playerChunkListeners[World::chunkHash($chunkX, $chunkZ)] ?? []; @@ -610,6 +709,7 @@ class World implements ChunkManager{ * Gets the chunk loaders being used in a specific chunk * * @return ChunkLoader[] + * @phpstan-return array */ public function getChunkLoaders(int $chunkX, int $chunkZ) : array{ return $this->chunkLoaders[World::chunkHash($chunkX, $chunkZ)] ?? []; @@ -618,7 +718,8 @@ class World implements ChunkManager{ /** * Returns an array of players who have the target position within their view distance. * - * @return Player[] + * @return Player[] spl_object_id => Player + * @phpstan-return array */ public function getViewersForPosition(Vector3 $pos) : array{ return $this->getChunkPlayers($pos->getFloorX() >> Chunk::COORD_BIT_SIZE, $pos->getFloorZ() >> Chunk::COORD_BIT_SIZE); @@ -733,6 +834,7 @@ class World implements ChunkManager{ * Returns all the listeners attached to this chunk. * * @return ChunkListener[] + * @phpstan-return array */ public function getChunkListeners(int $chunkX, int $chunkZ) : array{ return $this->chunkListeners[World::chunkHash($chunkX, $chunkZ)] ?? []; @@ -926,8 +1028,10 @@ class World implements ChunkManager{ /** * @param Vector3[] $blocks + * @phpstan-param list $blocks * * @return ClientboundPacket[] + * @phpstan-return list */ public function createBlockUpdatePackets(array $blocks) : array{ $packets = []; @@ -973,7 +1077,8 @@ class World implements ChunkManager{ } /** - * @return bool[] fullID => bool + * @return true[] fullID => dummy + * @phpstan-return array */ public function getRandomTickedBlocks() : array{ return $this->randomTickBlocks; @@ -1195,6 +1300,7 @@ class World implements ChunkManager{ /** * @return Block[] + * @phpstan-return list */ public function getCollisionBlocks(AxisAlignedBB $bb, bool $targetFirst = false) : array{ $minX = (int) floor($bb->minX - 1); @@ -1235,6 +1341,7 @@ class World implements ChunkManager{ /** * @return AxisAlignedBB[] + * @phpstan-return list */ public function getCollisionBoxes(Entity $entity, AxisAlignedBB $bb, bool $entities = true) : array{ $minX = (int) floor($bb->minX - 1); @@ -1536,17 +1643,15 @@ class World implements ChunkManager{ $block->position($this, $x, $y, $z); - static $dynamicStateRead = false; - - if($dynamicStateRead){ + if($this->inDynamicStateRecalculation){ //this call was generated by a parent getBlock() call calculating dynamic stateinfo //don't calculate dynamic state and don't add to block cache (since it won't have dynamic state calculated). //this ensures that it's impossible for dynamic state properties to recursively depend on each other. $addToCache = false; }else{ - $dynamicStateRead = true; + $this->inDynamicStateRecalculation = true; $block->readStateFromWorld(); - $dynamicStateRead = false; + $this->inDynamicStateRecalculation = false; } if($addToCache && $relativeBlockHash !== null){ @@ -1636,6 +1741,7 @@ class World implements ChunkManager{ * Drops XP orbs into the world for the specified amount, splitting the amount into several orbs if necessary. * * @return ExperienceOrb[] + * @phpstan-return list */ public function dropExperience(Vector3 $pos, int $amount) : array{ /** @var ExperienceOrb[] $orbs */ @@ -1888,18 +1994,24 @@ class World implements ChunkManager{ } /** - * Gets the list of all the entities in this world + * Returns a list of all the entities in this world, indexed by their entity runtime IDs * * @return Entity[] + * @phpstan-return array */ public function getEntities() : array{ return $this->entities; } /** - * Returns the entities colliding the current one inside the AxisAlignedBB + * Returns all collidable entities whose bounding boxes intersect the given bounding box. + * If an entity is given, it will be excluded from the result. + * If a non-collidable entity is given, the result will be empty. + * + * This function is the same as {@link World::getNearbyEntities()}, but with additional collidability filters. * * @return Entity[] + * @phpstan-return array */ public function getCollidingEntities(AxisAlignedBB $bb, ?Entity $entity = null) : array{ $nearby = []; @@ -1916,9 +2028,10 @@ class World implements ChunkManager{ } /** - * Returns the entities near the current one inside the AxisAlignedBB + * Returns all entities whose bounding boxes intersect the given bounding box, excluding the given entity. * * @return Entity[] + * @phpstan-return array */ public function getNearbyEntities(AxisAlignedBB $bb, ?Entity $entity = null) : array{ $nearby = []; @@ -1995,7 +2108,8 @@ class World implements ChunkManager{ /** * Returns a list of the players in this world * - * @return Player[] + * @return Player[] entity runtime ID => Player + * @phpstan-return array */ public function getPlayers() : array{ return $this->players; @@ -2042,7 +2156,8 @@ class World implements ChunkManager{ } /** - * @return Chunk[] + * @return Chunk[] chunkHash => Chunk + * @phpstan-return array */ public function getLoadedChunks() : array{ return $this->chunks; @@ -2053,7 +2168,8 @@ class World implements ChunkManager{ } /** - * @return Entity[] + * @return Entity[] entity runtime ID => Entity + * @phpstan-return array */ public function getChunkEntities(int $chunkX, int $chunkZ) : array{ return $this->entitiesByChunk[World::chunkHash($chunkX, $chunkZ)] ?? []; @@ -2069,7 +2185,8 @@ class World implements ChunkManager{ /** * Returns the chunks adjacent to the specified chunk. * - * @return Chunk[]|null[] + * @return Chunk[]|null[] chunkHash => Chunk|null + * @phpstan-return array */ public function getAdjacentChunks(int $x, int $z) : array{ $result = []; @@ -2492,7 +2609,7 @@ class World implements ChunkManager{ continue; } if($entity === null){ - $saveIdTag = $nbt->getTag("id") ?? $nbt->getTag("identifier"); + $saveIdTag = $nbt->getTag("identifier") ?? $nbt->getTag("id"); $saveId = ""; if($saveIdTag instanceof StringTag){ $saveId = $saveIdTag->getValue(); diff --git a/src/world/WorldManager.php b/src/world/WorldManager.php index 83f7aac98..1c26d494b 100644 --- a/src/world/WorldManager.php +++ b/src/world/WorldManager.php @@ -39,7 +39,7 @@ use pocketmine\world\format\io\WorldProviderManager; use pocketmine\world\format\io\WritableWorldProvider; use pocketmine\world\generator\GeneratorManager; use pocketmine\world\generator\InvalidGeneratorOptionsException; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function array_keys; use function array_shift; use function assert; diff --git a/src/world/format/io/FormatConverter.php b/src/world/format/io/FormatConverter.php index 48f4d67b9..5f93f3a7b 100644 --- a/src/world/format/io/FormatConverter.php +++ b/src/world/format/io/FormatConverter.php @@ -27,7 +27,7 @@ use pocketmine\utils\Filesystem; use pocketmine\world\generator\GeneratorManager; use pocketmine\world\generator\normal\Normal; use pocketmine\world\WorldCreationOptions; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function basename; use function crc32; use function file_exists; diff --git a/src/world/format/io/data/BedrockWorldData.php b/src/world/format/io/data/BedrockWorldData.php index b93acac68..995f64898 100644 --- a/src/world/format/io/data/BedrockWorldData.php +++ b/src/world/format/io/data/BedrockWorldData.php @@ -38,7 +38,7 @@ use pocketmine\world\generator\Flat; use pocketmine\world\generator\GeneratorManager; use pocketmine\world\World; use pocketmine\world\WorldCreationOptions; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function file_get_contents; use function file_put_contents; use function strlen; diff --git a/src/world/format/io/data/JavaWorldData.php b/src/world/format/io/data/JavaWorldData.php index e57bbe941..e53d857ad 100644 --- a/src/world/format/io/data/JavaWorldData.php +++ b/src/world/format/io/data/JavaWorldData.php @@ -35,7 +35,7 @@ use pocketmine\world\format\io\exception\CorruptedWorldException; use pocketmine\world\generator\GeneratorManager; use pocketmine\world\World; use pocketmine\world\WorldCreationOptions; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function ceil; use function file_get_contents; use function file_put_contents; diff --git a/src/world/format/io/leveldb/LevelDB.php b/src/world/format/io/leveldb/LevelDB.php index 5adcd0103..c2204582a 100644 --- a/src/world/format/io/leveldb/LevelDB.php +++ b/src/world/format/io/leveldb/LevelDB.php @@ -50,7 +50,7 @@ use pocketmine\world\format\io\WritableWorldProvider; use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\format\SubChunk; use pocketmine\world\WorldCreationOptions; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function array_map; use function array_values; use function chr; diff --git a/src/world/format/io/region/RegionWorldProvider.php b/src/world/format/io/region/RegionWorldProvider.php index 91a7937a6..f02b1d790 100644 --- a/src/world/format/io/region/RegionWorldProvider.php +++ b/src/world/format/io/region/RegionWorldProvider.php @@ -32,7 +32,7 @@ use pocketmine\world\format\io\ChunkData; use pocketmine\world\format\io\data\JavaWorldData; use pocketmine\world\format\io\exception\CorruptedChunkException; use pocketmine\world\format\io\WorldData; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function assert; use function file_exists; use function is_dir; diff --git a/src/world/format/io/region/WritableRegionWorldProvider.php b/src/world/format/io/region/WritableRegionWorldProvider.php index fd4399474..56cc2ff71 100644 --- a/src/world/format/io/region/WritableRegionWorldProvider.php +++ b/src/world/format/io/region/WritableRegionWorldProvider.php @@ -27,7 +27,7 @@ use pocketmine\world\format\io\ChunkData; use pocketmine\world\format\io\data\JavaWorldData; use pocketmine\world\format\io\WritableWorldProvider; use pocketmine\world\WorldCreationOptions; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function file_exists; use function mkdir; diff --git a/src/world/generator/normal/Normal.php b/src/world/generator/normal/Normal.php index 2a89d1e61..0e520c8ae 100644 --- a/src/world/generator/normal/Normal.php +++ b/src/world/generator/normal/Normal.php @@ -123,6 +123,7 @@ class Normal extends Generator{ private function pickBiome(int $x, int $z) : Biome{ $hash = $x * 2345803 ^ $z * 9236449 ^ $this->seed; $hash *= $hash + 223; + $hash = (int) $hash; $xNoise = $hash >> 20 & 3; $zNoise = $hash >> 22 & 3; if($xNoise == 3){ diff --git a/src/world/sound/BurpSound.php b/src/world/sound/BurpSound.php new file mode 100644 index 000000000..a4467f71d --- /dev/null +++ b/src/world/sound/BurpSound.php @@ -0,0 +1,35 @@ + [ + 'phpVersion' => PHP_VERSION_ID + ] +]; diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 746c9a792..1121cd134 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -335,6 +335,21 @@ parameters: count: 3 path: ../../../src/block/Mycelium.php + - + message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: ../../../src/block/RedMushroom.php + + - + message: "#^Parameter \\#2 \\$y of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: ../../../src/block/RedMushroom.php + + - + message: "#^Parameter \\#3 \\$z of method pocketmine\\\\world\\\\World\\:\\:getFullLightAt\\(\\) expects int, float\\|int given\\.$#" + count: 1 + path: ../../../src/block/RedMushroom.php + - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockLightAt\\(\\) expects int, float\\|int given\\.$#" count: 1 @@ -510,11 +525,6 @@ parameters: count: 1 path: ../../../src/crash/CrashDump.php - - - message: "#^Parameter \\#1 \\$index of method pocketmine\\\\inventory\\\\BaseInventory\\:\\:setItem\\(\\) expects int, int\\|string given\\.$#" - count: 1 - path: ../../../src/entity/ExperienceManager.php - - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getBlockAt\\(\\) expects int, float\\|int given\\.$#" count: 1 @@ -775,18 +785,8 @@ parameters: count: 1 path: ../../../src/plugin/PluginBase.php - - - message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" - count: 1 - path: ../../../src/plugin/PluginDescription.php - - message: "#^Cannot cast mixed to string\\.$#" - count: 5 - path: ../../../src/plugin/PluginDescription.php - - - - message: "#^Parameter \\#1 \\$data of static method pocketmine\\\\permission\\\\PermissionParser\\:\\:loadPermissions\\(\\) expects array\\\\>, mixed given\\.$#" count: 1 path: ../../../src/plugin/PluginDescription.php @@ -795,11 +795,6 @@ parameters: count: 1 path: ../../../src/plugin/PluginDescription.php - - - message: "#^Parameter \\#1 \\$name of static method pocketmine\\\\plugin\\\\PluginEnableOrder\\:\\:fromString\\(\\) expects string, mixed given\\.$#" - count: 1 - path: ../../../src/plugin/PluginDescription.php - - message: "#^Parameter \\#2 \\$subject of function preg_match expects string, mixed given\\.$#" count: 1 @@ -810,26 +805,6 @@ parameters: count: 1 path: ../../../src/plugin/PluginDescription.php - - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$authors \\(array\\\\) does not accept array\\.$#" - count: 1 - path: ../../../src/plugin/PluginDescription.php - - - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$authors \\(array\\\\) does not accept array\\\\.$#" - count: 1 - path: ../../../src/plugin/PluginDescription.php - - - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$depend \\(array\\\\) does not accept array\\.$#" - count: 1 - path: ../../../src/plugin/PluginDescription.php - - - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$loadBefore \\(array\\\\) does not accept array\\.$#" - count: 1 - path: ../../../src/plugin/PluginDescription.php - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$main \\(string\\) does not accept mixed\\.$#" count: 1 @@ -840,16 +815,6 @@ parameters: count: 1 path: ../../../src/plugin/PluginDescription.php - - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$softDepend \\(array\\\\) does not accept array\\.$#" - count: 1 - path: ../../../src/plugin/PluginDescription.php - - - - message: "#^Property pocketmine\\\\plugin\\\\PluginDescription\\:\\:\\$srcNamespacePrefix \\(string\\) does not accept mixed\\.$#" - count: 1 - path: ../../../src/plugin/PluginDescription.php - - message: "#^Cannot call method addChild\\(\\) on pocketmine\\\\permission\\\\Permission\\|null\\.$#" count: 4 diff --git a/tests/phpstan/configs/impossible-generics.neon b/tests/phpstan/configs/impossible-generics.neon index 0281bcd7f..b0e67d294 100644 --- a/tests/phpstan/configs/impossible-generics.neon +++ b/tests/phpstan/configs/impossible-generics.neon @@ -10,8 +10,3 @@ parameters: count: 1 path: ../../../src/event/RegisteredListener.php - - - message: "#^Property pocketmine\\\\event\\\\RegisteredListener\\:\\:\\$handler type has no signature specified for Closure\\.$#" - count: 1 - path: ../../../src/event/RegisteredListener.php - diff --git a/tests/phpstan/configs/php-bugs.neon b/tests/phpstan/configs/php-bugs.neon index f4754087b..4475ec468 100644 --- a/tests/phpstan/configs/php-bugs.neon +++ b/tests/phpstan/configs/php-bugs.neon @@ -1,7 +1,6 @@ parameters: ignoreErrors: - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\handler\\\\StupidJsonDecodeTest\\:\\:\\$stupidJsonDecodeFunc \\(Closure\\(string, bool\\)\\: mixed\\) does not accept Closure\\|null\\.$#" + message: "#^Property pocketmine\\\\network\\\\mcpe\\\\handler\\\\StupidJsonDecodeTest\\:\\:\\$stupidJsonDecodeFunc \\(Closure\\(string, bool\\=\\)\\: mixed\\) does not accept Closure\\|null\\.$#" count: 1 path: ../../phpunit/network/mcpe/handler/StupidJsonDecodeTest.php - diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index a1c53d2a4..e50532cfd 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -10,16 +10,6 @@ parameters: count: 1 path: ../../../src/console/ConsoleCommandSender.php - - - message: "#^Method pocketmine\\\\crafting\\\\CraftingManager\\:\\:getDestructorCallbacks\\(\\) should return pocketmine\\\\utils\\\\ObjectSet\\ but returns pocketmine\\\\utils\\\\ObjectSet\\\\|pocketmine\\\\utils\\\\ObjectSet\\\\.$#" - count: 1 - path: ../../../src/crafting/CraftingManager.php - - - - message: "#^Property pocketmine\\\\crafting\\\\CraftingManager\\:\\:\\$destructorCallbacks \\(pocketmine\\\\utils\\\\ObjectSet\\\\|null\\) does not accept pocketmine\\\\utils\\\\ObjectSet\\\\.$#" - count: 1 - path: ../../../src/crafting/CraftingManager.php - - message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#" count: 1 @@ -35,11 +25,6 @@ parameters: count: 1 path: ../../../src/network/mcpe/handler/InGamePacketHandler.php - - - message: "#^Negated boolean expression is always true\\.$#" - count: 1 - path: ../../../src/network/mcpe/handler/InGamePacketHandler.php - - message: "#^Property pocketmine\\\\network\\\\mcpe\\\\raklib\\\\PthreadsChannelWriter\\:\\:\\$buffer is never read, only written\\.$#" count: 1 @@ -60,6 +45,11 @@ parameters: count: 1 path: ../../../src/plugin/PluginManager.php + - + message: "#^Offset \\(int\\|string\\) on non\\-empty\\-array\\ in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: ../../../src/plugin/PluginManager.php + - message: "#^Static property pocketmine\\\\scheduler\\\\AsyncTask\\:\\:\\$threadLocalStorage \\(ArrayObject\\\\>\\|null\\) does not accept non\\-empty\\-array\\\\>\\|ArrayObject\\\\>\\.$#" count: 1 @@ -75,8 +65,18 @@ parameters: count: 1 path: ../../../src/thread/Worker.php + - + message: "#^Offset \\(int\\|string\\) on non\\-empty\\-array\\ in isset\\(\\) always exists and is not nullable\\.$#" + count: 1 + path: ../../../src/world/WorldManager.php + - message: "#^Call to function is_resource\\(\\) with resource will always evaluate to true\\.$#" count: 2 path: ../../../src/world/format/io/region/RegionLoader.php + - + message: "#^Casting to int something that's already int\\.$#" + count: 1 + path: ../../../src/world/generator/normal/Normal.php + diff --git a/tests/phpunit/block/BrewingStandTest.php b/tests/phpunit/block/BrewingStandTest.php index 58be37470..c61a1db6d 100644 --- a/tests/phpunit/block/BrewingStandTest.php +++ b/tests/phpunit/block/BrewingStandTest.php @@ -25,6 +25,7 @@ namespace pocketmine\block; use PHPUnit\Framework\TestCase; use pocketmine\block\utils\BrewingStandSlot; +use function array_values; use function count; class BrewingStandTest extends TestCase{ @@ -33,7 +34,7 @@ class BrewingStandTest extends TestCase{ * @phpstan-return \Generator}, void, void> */ public function slotsProvider() : \Generator{ - yield [BrewingStandSlot::getAll()]; + yield [array_values(BrewingStandSlot::getAll())]; yield [[BrewingStandSlot::EAST()]]; yield [[BrewingStandSlot::EAST(), BrewingStandSlot::NORTHWEST()]]; } diff --git a/tests/phpunit/world/format/io/region/RegionLoaderTest.php b/tests/phpunit/world/format/io/region/RegionLoaderTest.php index abf73a0f5..e4db90072 100644 --- a/tests/phpunit/world/format/io/region/RegionLoaderTest.php +++ b/tests/phpunit/world/format/io/region/RegionLoaderTest.php @@ -25,7 +25,7 @@ namespace pocketmine\world\format\io\region; use PHPUnit\Framework\TestCase; use pocketmine\world\format\ChunkException; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function bin2hex; use function clearstatcache; use function file_exists; diff --git a/tests/plugins/DevTools b/tests/plugins/DevTools index e884a4c23..bd0fa048d 160000 --- a/tests/plugins/DevTools +++ b/tests/plugins/DevTools @@ -1 +1 @@ -Subproject commit e884a4c234629126203e769df7c4dbbbc0dc2d49 +Subproject commit bd0fa048dae29bebe25ba76d8c58788e92418b67 diff --git a/tools/generate-permission-doc.php b/tools/generate-permission-doc.php index 7d66740aa..fd04f1a29 100644 --- a/tools/generate-permission-doc.php +++ b/tools/generate-permission-doc.php @@ -27,7 +27,7 @@ use pocketmine\permission\DefaultPermissions; use pocketmine\permission\PermissionManager; use pocketmine\utils\Utils; use pocketmine\VersionInfo; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function count; use function dirname; use function fclose; diff --git a/tools/simulate-chunk-selector.php b/tools/simulate-chunk-selector.php index e360a96e9..81beb6bb3 100644 --- a/tools/simulate-chunk-selector.php +++ b/tools/simulate-chunk-selector.php @@ -28,7 +28,7 @@ use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Utils; use pocketmine\world\format\Chunk; use pocketmine\world\World; -use Webmozart\PathUtil\Path; +use Symfony\Component\Filesystem\Path; use function assert; use function count; use function dirname;