diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index a9e6a2e31..8e1ba493e 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.0 + uses: docker/build-push-action@v3.1.1 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.0 + uses: docker/build-push-action@v3.1.1 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.0 + uses: docker/build-push-action@v3.1.1 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.0 + uses: docker/build-push-action@v3.1.1 with: push: true context: ./pocketmine-mp diff --git a/.github/workflows/discord-release-embed.php b/.github/workflows/discord-release-embed.php new file mode 100644 index 000000000..41ef89878 --- /dev/null +++ b/.github/workflows/discord-release-embed.php @@ -0,0 +1,100 @@ + + */ +function generateDiscordEmbed(string $version, string $channel, string $description, string $detailsUrl, string $sourceUrl, string $pharDownloadUrl, string $buildLogUrl) : array{ + return [ + "embeds" => [ + [ + "title" => "New PocketMine-MP release: $version ($channel)", + "description" => << $detailsUrl, + "color" => $channel === "stable" ? 0x57ab5a : 0xc69026 + ] + ] + ]; +} + +if(count($argv) !== 5){ + fwrite(STDERR, "Required arguments: github repo, version, API token\n"); + exit(1); +} +[, $repo, $tagName, $token, $hookURL] = $argv; + +$result = Internet::getURL('https://api.github.com/repos/' . $repo . '/releases/tags/' . $tagName, extraHeaders: [ + 'Authorization: token ' . $token +]); +if($result === null){ + fwrite(STDERR, "failed to access GitHub API\n"); + return; +} +if($result->getCode() !== 200){ + fwrite(STDERR, "Error accessing GitHub API: " . $result->getCode() . "\n"); + fwrite(STDERR, $result->getBody() . "\n"); + exit(1); +} + +$releaseInfoJson = json_decode($result->getBody(), true, JSON_THROW_ON_ERROR); +if(!is_array($releaseInfoJson)){ + fwrite(STDERR, "Invalid release JSON returned from GitHub API\n"); + exit(1); +} +$buildInfoPath = 'https://github.com/' . $repo . '/releases/download/' . $tagName . '/build_info.json'; + +$buildInfoResult = Internet::getURL($buildInfoPath, extraHeaders: [ + 'Authorization: token ' . $token +]); +if($buildInfoResult === null){ + fwrite(STDERR, "missing build_info.json\n"); + exit(1); +} +if($buildInfoResult->getCode() !== 200){ + fwrite(STDERR, "error accessing build_info.json: " . $buildInfoResult->getCode() . "\n"); + fwrite(STDERR, $buildInfoResult->getBody() . "\n"); + exit(1); +} + +$buildInfoJson = json_decode($buildInfoResult->getBody(), true, JSON_THROW_ON_ERROR); +if(!is_array($buildInfoJson)){ + fwrite(STDERR, "invalid build_info.json\n"); + exit(1); +} +$detailsUrl = $buildInfoJson["details_url"]; +$sourceUrl = $buildInfoJson["source_url"]; +$pharDownloadUrl = $buildInfoJson["download_url"]; +$buildLogUrl = $buildInfoJson["build_log_url"]; + +$description = $releaseInfoJson["body"]; + +$discordPayload = generateDiscordEmbed($buildInfoJson["base_version"], $buildInfoJson["channel"], $description, $detailsUrl, $sourceUrl, $pharDownloadUrl, $buildLogUrl); + +$response = Internet::postURL( + $hookURL, + json_encode($discordPayload, JSON_THROW_ON_ERROR), + extraHeaders: ['Content-Type: application/json'] +); +if($response?->getCode() !== 204){ + fwrite(STDERR, "failed to send Discord webhook\n"); + fwrite(STDERR, $response?->getBody() ?? "no response body\n"); + exit(1); +} diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml new file mode 100644 index 000000000..210f5c0d9 --- /dev/null +++ b/.github/workflows/discord-release-notify.yml @@ -0,0 +1,38 @@ +name: Notify Discord webhook of release + +on: + release: + types: + - published + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP and tools + uses: shivammathur/setup-php@2.21.1 + with: + php-version: 8.0 + + - name: Restore Composer package cache + uses: actions/cache@v3 + with: + path: | + ~/.cache/composer/files + ~/.cache/composer/vcs + key: "composer-v2-cache-${{ hashFiles('./composer.lock') }}" + restore-keys: | + composer-v2-cache- + + - name: Install Composer dependencies + run: composer install --no-dev --prefer-dist --no-interaction --ignore-platform-reqs + + - name: Get actual tag name + id: tag-name + run: echo ::set-output name=TAG_NAME::$(echo "${{ github.ref }}" | sed 's{^refs/tags/{{') + + - name: Run webhook post script + run: php .github/workflows/discord-release-embed.php ${{ github.repo }} ${{ steps.tag-name.outputs.TAG_NAME }} ${{ github.token }} ${{ secrets.DISCORD_RELEASE_WEBHOOK }} diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 9cb10390e..ceb3b1d9d 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.0 + uses: shivammathur/setup-php@2.21.1 with: php-version: 8.0 @@ -57,7 +57,7 @@ jobs: echo ::set-output name=PM_VERSION_MD::$(php -r 'require "vendor/autoload.php"; echo str_replace(".", "", \pocketmine\VersionInfo::BASE_VERSION);') - name: Generate build info - run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ steps.build-number.outputs.BUILD_NUMBER }} > build_info.json + run: php build/generate-build-info-json.php ${{ github.sha }} ${{ steps.get-pm-version.outputs.PM_VERSION }} ${{ github.repository }} ${{ steps.build-number.outputs.BUILD_NUMBER }} ${{ github.run_id }} > build_info.json - name: Upload release artifacts uses: actions/upload-artifact@v3 @@ -80,4 +80,4 @@ jobs: body: | **For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}** - Please see the [changelogs](/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details. + Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 08b4e253c..b46d7f8eb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -195,7 +195,7 @@ jobs: - uses: actions/checkout@v3 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.21.0 + uses: shivammathur/setup-php@2.21.1 with: php-version: 8.0 tools: php-cs-fixer:3.2 diff --git a/build/generate-build-info-json.php b/build/generate-build-info-json.php index b4e92f7d6..f0ffe7df8 100644 --- a/build/generate-build-info-json.php +++ b/build/generate-build-info-json.php @@ -23,8 +23,8 @@ declare(strict_types=1); require dirname(__DIR__) . '/vendor/autoload.php'; -if(count($argv) !== 5){ - fwrite(STDERR, "required args: "); +if(count($argv) !== 6){ + fwrite(STDERR, "required args: \n"); exit(1); } @@ -40,4 +40,5 @@ echo json_encode([ "details_url" => "https://github.com/$argv[3]/releases/tag/$argv[2]", "download_url" => "https://github.com/$argv[3]/releases/download/$argv[2]/PocketMine-MP.phar", "source_url" => "https://github.com/$argv[3]/tree/$argv[2]", + "build_log_url" => "https://github.com/$argv[3]/actions/runs/$argv[5]", ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR) . "\n"; diff --git a/changelogs/4.6.md b/changelogs/4.6.md index e32098da6..82fc1af8f 100644 --- a/changelogs/4.6.md +++ b/changelogs/4.6.md @@ -29,3 +29,14 @@ Released 22nd July 2022. - Fixed incorrect fire ticks when being set on fire by lava (8 seconds in Bedrock instead of 15). - `Entity->attack()` now cancels damage from `FIRE` and `FIRE_TICK` damage causes if the entity is fireproof. - Fixed inventory windows getting force-closed when the client attempts to use an enchanting table or anvil. + +# 4.6.2 +Released 6th August 2022. + +## Core +- Improved server-side performance of `PlayerAuthInputPacket` handler. +- Improved client-side performance of `FloatingTextParticle` by using an invisible falling block entity. This offered a roughly 5x performance improvement over using tiny invisible players in local testing. + +## Fixes +- Fixed assert failures and debug spam on debug Minecraft clients related to abilities in `AddPlayerPacket`. +- Fixed crash in `ReversePriorityQueue` on PHP 8.1 by adding `#[ReturnTypeWillChange]` attribute. diff --git a/changelogs/4.7.md b/changelogs/4.7.md new file mode 100644 index 000000000..b450f7cf4 --- /dev/null +++ b/changelogs/4.7.md @@ -0,0 +1,14 @@ +**For Minecraft: Bedrock Edition 1.19.20** + +### 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.7.0 +Released 9th August 2022. + +## General +- Added support for Minecraft: Bedrock Edition 1.19.20. +- Removed support for older versions. diff --git a/composer.json b/composer.json index 3e925b4c7..5334687cf 100644 --- a/composer.json +++ b/composer.json @@ -34,8 +34,8 @@ "adhocore/json-comment": "^1.1", "fgrosse/phpasn1": "^2.3", "netresearch/jsonmapper": "^4.0", - "pocketmine/bedrock-data": "~1.9.0+bedrock-1.19.10", - "pocketmine/bedrock-protocol": "~11.0.0+bedrock-1.19.10", + "pocketmine/bedrock-data": "~1.10.0+bedrock-1.19.20", + "pocketmine/bedrock-protocol": "~12.0.0+bedrock-1.19.20", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/classloader": "^0.2.0", @@ -53,7 +53,7 @@ "webmozart/path-util": "^2.3" }, "require-dev": { - "phpstan/phpstan": "1.8.0", + "phpstan/phpstan": "1.8.2", "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 ad83e7609..34cce5d63 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e03c7f446cee1fdb97ee79817c78470a", + "content-hash": "80afa24adf37096a23643e051d6128ce", "packages": [ { "name": "adhocore/json-comment", @@ -63,26 +63,26 @@ }, { "name": "brick/math", - "version": "0.9.3", + "version": "0.10.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae" + "reference": "de846578401f4e58f911b3afeb62ced56365ed87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae", - "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae", + "url": "https://api.github.com/repos/brick/math/zipball/de846578401f4e58f911b3afeb62ced56365ed87", + "reference": "de846578401f4e58f911b3afeb62ced56365ed87", "shasum": "" }, "require": { "ext-json": "*", - "php": "^7.1 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", - "vimeo/psalm": "4.9.2" + "phpunit/phpunit": "^9.0", + "vimeo/psalm": "4.25.0" }, "type": "library", "autoload": { @@ -107,19 +107,15 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.9.3" + "source": "https://github.com/brick/math/tree/0.10.1" }, "funding": [ { "url": "https://github.com/BenMorel", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/brick/math", - "type": "tidelift" } ], - "time": "2021-08-15T20:50:18+00:00" + "time": "2022-08-01T22:54:31+00:00" }, { "name": "fgrosse/phpasn1", @@ -249,16 +245,16 @@ }, { "name": "pocketmine/bedrock-data", - "version": "1.9.0+bedrock-1.19.10", + "version": "1.10.0+bedrock-1.19.20", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockData.git", - "reference": "ecd798a3e7ead50b7da73141bbb0c4ba14dd76a1" + "reference": "43610f6749f22d15ede6b60ed5402bdeff47453e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/ecd798a3e7ead50b7da73141bbb0c4ba14dd76a1", - "reference": "ecd798a3e7ead50b7da73141bbb0c4ba14dd76a1", + "url": "https://api.github.com/repos/pmmp/BedrockData/zipball/43610f6749f22d15ede6b60ed5402bdeff47453e", + "reference": "43610f6749f22d15ede6b60ed5402bdeff47453e", "shasum": "" }, "type": "library", @@ -269,22 +265,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.10" + "source": "https://github.com/pmmp/BedrockData/tree/bedrock-1.19.20" }, - "time": "2022-07-12T19:33:21+00:00" + "time": "2022-08-09T17:44:22+00:00" }, { "name": "pocketmine/bedrock-protocol", - "version": "11.0.0+bedrock-1.19.10", + "version": "12.0.0+bedrock-1.19.20", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "705f928bd010ba093d8781d20006e4cd5f79f335" + "reference": "c2778039544fa0c7c5bd3af7963149e7552f4215" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/705f928bd010ba093d8781d20006e4cd5f79f335", - "reference": "705f928bd010ba093d8781d20006e4cd5f79f335", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/c2778039544fa0c7c5bd3af7963149e7552f4215", + "reference": "c2778039544fa0c7c5bd3af7963149e7552f4215", "shasum": "" }, "require": { @@ -316,9 +312,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/11.0.0+bedrock-1.19.10" + "source": "https://github.com/pmmp/BedrockProtocol/tree/bedrock-1.19.20" }, - "time": "2022-07-12T23:47:47+00:00" + "time": "2022-08-09T17:57:29+00:00" }, { "name": "pocketmine/binaryutils", @@ -930,20 +926,20 @@ }, { "name": "ramsey/uuid", - "version": "4.3.1", + "version": "4.4.0", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28" + "reference": "373f7bacfcf3de038778ff27dcce5672ddbf4c8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", - "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/373f7bacfcf3de038778ff27dcce5672ddbf4c8a", + "reference": "373f7bacfcf3de038778ff27dcce5672ddbf4c8a", "shasum": "" }, "require": { - "brick/math": "^0.8 || ^0.9", + "brick/math": "^0.8 || ^0.9 || ^0.10", "ext-ctype": "*", "ext-json": "*", "php": "^8.0", @@ -959,7 +955,6 @@ "doctrine/annotations": "^1.8", "ergebnis/composer-normalize": "^2.15", "mockery/mockery": "^1.3", - "moontoast/math": "^1.1", "paragonie/random-lib": "^2", "php-mock/php-mock": "^2.2", "php-mock/php-mock-mockery": "^1.3", @@ -1008,7 +1003,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.3.1" + "source": "https://github.com/ramsey/uuid/tree/4.4.0" }, "funding": [ { @@ -1020,7 +1015,7 @@ "type": "tidelift" } ], - "time": "2022-03-27T21:42:02+00:00" + "time": "2022-08-05T17:58:37+00:00" }, { "name": "symfony/polyfill-php81", @@ -1737,16 +1732,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.8.0", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "b7648d4ee9321665acaf112e49da9fd93df8fbd5" + "reference": "c53312ecc575caf07b0e90dee43883fdf90ca67c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b7648d4ee9321665acaf112e49da9fd93df8fbd5", - "reference": "b7648d4ee9321665acaf112e49da9fd93df8fbd5", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c53312ecc575caf07b0e90dee43883fdf90ca67c", + "reference": "c53312ecc575caf07b0e90dee43883fdf90ca67c", "shasum": "" }, "require": { @@ -1772,7 +1767,7 @@ "description": "PHPStan - PHP Static Analysis Tool", "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.0" + "source": "https://github.com/phpstan/phpstan/tree/1.8.2" }, "funding": [ { @@ -1792,7 +1787,7 @@ "type": "tidelift" } ], - "time": "2022-06-29T08:53:31+00:00" + "time": "2022-07-20T09:57:31+00:00" }, { "name": "phpstan/phpstan-phpunit", diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 20020cb25..9aeb3fde8 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.6.2"; + public const BASE_VERSION = "4.7.1"; public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "beta"; diff --git a/src/crafting/CraftingManagerFromDataHelper.php b/src/crafting/CraftingManagerFromDataHelper.php index 05612e66d..e91cb4ba6 100644 --- a/src/crafting/CraftingManagerFromDataHelper.php +++ b/src/crafting/CraftingManagerFromDataHelper.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\crafting; use pocketmine\item\Item; +use pocketmine\item\ItemFactory; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Utils; use function array_map; @@ -33,6 +34,23 @@ use function json_decode; final class CraftingManagerFromDataHelper{ + /** + * @param Item[] $items + */ + private static function containsUnknownOutputs(array $items) : bool{ + $factory = ItemFactory::getInstance(); + foreach($items as $item){ + if($item->hasAnyDamageValue()){ + throw new \InvalidArgumentException("Recipe outputs must not have wildcard meta values"); + } + if(!$factory->isRegistered($item->getId(), $item->getMeta())){ + return true; + } + } + + return false; + } + public static function make(string $filePath) : CraftingManager{ $recipes = json_decode(Utils::assumeNotFalse(file_get_contents($filePath), "Missing required resource file"), true); if(!is_array($recipes)){ @@ -52,9 +70,13 @@ final class CraftingManagerFromDataHelper{ if($recipeType === null){ continue; } + $output = array_map($itemDeserializerFunc, $recipe["output"]); + if(self::containsUnknownOutputs($output)){ + continue; + } $result->registerShapelessRecipe(new ShapelessRecipe( array_map($itemDeserializerFunc, $recipe["input"]), - array_map($itemDeserializerFunc, $recipe["output"]), + $output, $recipeType )); } @@ -62,10 +84,14 @@ final class CraftingManagerFromDataHelper{ if($recipe["block"] !== "crafting_table"){ //TODO: filter others out for now to avoid breaking economics continue; } + $output = array_map($itemDeserializerFunc, $recipe["output"]); + if(self::containsUnknownOutputs($output)){ + continue; + } $result->registerShapedRecipe(new ShapedRecipe( $recipe["shape"], array_map($itemDeserializerFunc, $recipe["input"]), - array_map($itemDeserializerFunc, $recipe["output"]) + $output )); } foreach($recipes["smelting"] as $recipe){ @@ -79,19 +105,30 @@ final class CraftingManagerFromDataHelper{ if($furnaceType === null){ continue; } + $output = Item::jsonDeserialize($recipe["output"]); + if(self::containsUnknownOutputs([$output])){ + continue; + } $result->getFurnaceRecipeManager($furnaceType)->register(new FurnaceRecipe( - Item::jsonDeserialize($recipe["output"]), + $output, Item::jsonDeserialize($recipe["input"])) ); } foreach($recipes["potion_type"] as $recipe){ + $output = Item::jsonDeserialize($recipe["output"]); + if(self::containsUnknownOutputs([$output])){ + continue; + } $result->registerPotionTypeRecipe(new PotionTypeRecipe( Item::jsonDeserialize($recipe["input"]), Item::jsonDeserialize($recipe["ingredient"]), - Item::jsonDeserialize($recipe["output"]) + $output )); } foreach($recipes["potion_container_change"] as $recipe){ + if(!ItemFactory::getInstance()->isRegistered($recipe["output_item_id"])){ + continue; + } $result->registerPotionContainerChangeRecipe(new PotionContainerChangeRecipe( $recipe["input_item_id"], Item::jsonDeserialize($recipe["ingredient"]), diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 40382c564..54873f9f1 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -1466,7 +1466,7 @@ abstract class Entity{ $this->location->yaw, //TODO: head yaw $this->location->yaw, //TODO: body yaw (wtf mojang?) array_map(function(Attribute $attr) : NetworkAttribute{ - return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue()); + return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []); }, $this->attributeMap->getAll()), $this->getAllNetworkData(), [] //TODO: entity links diff --git a/src/item/ItemFactory.php b/src/item/ItemFactory.php index eefb5eb95..4a949f843 100644 --- a/src/item/ItemFactory.php +++ b/src/item/ItemFactory.php @@ -472,7 +472,7 @@ class ItemFactory{ if(isset($this->list[$offset = self::getListOffset($id, $meta)])){ $item = clone $this->list[$offset]; }elseif(isset($this->list[$zero = self::getListOffset($id, 0)]) && $this->list[$zero] instanceof Durable){ - if($meta <= $this->list[$zero]->getMaxDurability()){ + if($meta >= 0 && $meta <= $this->list[$zero]->getMaxDurability()){ $item = clone $this->list[$zero]; $item->setDamage($meta); }else{ diff --git a/src/item/PotionType.php b/src/item/PotionType.php index 1dab192dc..7ec0f3876 100644 --- a/src/item/PotionType.php +++ b/src/item/PotionType.php @@ -185,13 +185,16 @@ final class PotionType{ new EffectInstance(VanillaEffects::WITHER(), 800, 1) ]), new self("turtle_master", "Turtle Master", fn() => [ - //TODO + new EffectInstance(VanillaEffects::SLOWNESS(), 20 * 20, 3), + new EffectInstance(VanillaEffects::RESISTANCE(), 20 * 20, 2), ]), new self("long_turtle_master", "Long Turtle Master", fn() => [ - //TODO + new EffectInstance(VanillaEffects::SLOWNESS(), 40 * 20, 3), + new EffectInstance(VanillaEffects::RESISTANCE(), 40 * 20, 2), ]), new self("strong_turtle_master", "Strong Turtle Master", fn() => [ - //TODO + new EffectInstance(VanillaEffects::SLOWNESS(), 20 * 20, 5), + new EffectInstance(VanillaEffects::RESISTANCE(), 20 * 20, 3), ]), new self("slow_falling", "Slow Falling", fn() => [ //TODO diff --git a/src/network/mcpe/ChunkRequestTask.php b/src/network/mcpe/ChunkRequestTask.php index 1be39e978..f66614099 100644 --- a/src/network/mcpe/ChunkRequestTask.php +++ b/src/network/mcpe/ChunkRequestTask.php @@ -30,6 +30,7 @@ use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\network\mcpe\protocol\LevelChunkPacket; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; +use pocketmine\network\mcpe\protocol\types\ChunkPosition; use pocketmine\network\mcpe\serializer\ChunkSerializer; use pocketmine\scheduler\AsyncTask; use pocketmine\world\format\Chunk; @@ -71,7 +72,7 @@ class ChunkRequestTask extends AsyncTask{ $subCount = ChunkSerializer::getSubChunkCount($chunk) + ChunkSerializer::LOWER_PADDING_SIZE; $encoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); $payload = ChunkSerializer::serializeFullChunk($chunk, RuntimeBlockMapping::getInstance(), $encoderContext, $this->tiles); - $this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create($this->chunkX, $this->chunkZ, $subCount, false, null, $payload))->getBuffer())); + $this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload))->getBuffer())); } public function onError() : void{ diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index ee965cfa1..6f9286920 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; -use pocketmine\block\tile\Spawnable; use pocketmine\data\bedrock\EffectIdMap; use pocketmine\entity\Attribute; use pocketmine\entity\effect\EffectInstance; @@ -59,7 +58,6 @@ use pocketmine\network\mcpe\handler\PreSpawnPacketHandler; use pocketmine\network\mcpe\handler\ResourcePacksPacketHandler; use pocketmine\network\mcpe\handler\SpawnResponsePacketHandler; use pocketmine\network\mcpe\protocol\AvailableCommandsPacket; -use pocketmine\network\mcpe\protocol\BlockActorDataPacket; use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\DisconnectPacket; @@ -763,7 +761,7 @@ class NetworkSession{ } public function syncViewAreaCenterPoint(Vector3 $newPos, int $viewDistance) : void{ - $this->sendDataPacket(NetworkChunkPublisherUpdatePacket::create(BlockPosition::fromVector3($newPos), $viewDistance * 16)); //blocks, not chunks >.> + $this->sendDataPacket(NetworkChunkPublisherUpdatePacket::create(BlockPosition::fromVector3($newPos), $viewDistance * 16, [])); //blocks, not chunks >.> } public function syncPlayerSpawnPoint(Position $newSpawn) : void{ @@ -841,7 +839,7 @@ class NetworkSession{ public function syncAttributes(Living $entity, array $attributes) : void{ if(count($attributes) > 0){ $this->sendDataPacket(UpdateAttributesPacket::create($entity->getId(), array_map(function(Attribute $attr) : NetworkAttribute{ - return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue()); + return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []); }, $attributes), 0)); } } @@ -968,22 +966,6 @@ class NetworkSession{ try{ $this->queueCompressed($promise); $onCompletion(); - - //TODO: HACK! we send the full tile data here, due to a bug in 1.19.10 which causes items in tiles - //(item frames, lecterns) to not load properly when they are sent in a chunk via the classic chunk - //sending mechanism. We workaround this bug by sending only bare essential data in LevelChunkPacket - //(enough to create the tiles, since BlockActorDataPacket can't create tiles by itself) and then - //send the actual tile properties here. - //TODO: maybe we can stuff these packets inside the cached batch alongside LevelChunkPacket? - $chunk = $currentWorld->getChunk($chunkX, $chunkZ); - if($chunk !== null){ - foreach($chunk->getTiles() as $tile){ - if(!($tile instanceof Spawnable)){ - continue; - } - $this->sendDataPacket(BlockActorDataPacket::create(BlockPosition::fromVector3($tile->getPosition()), $tile->getSerializedSpawnCompound())); - } - } }finally{ $world->timings->syncChunkSend->stopTiming(); } diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index ebc8d3cc1..7a8caabfe 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -885,7 +885,14 @@ class InGamePacketHandler extends PacketHandler{ } public function handleModalFormResponse(ModalFormResponsePacket $packet) : bool{ - return $this->player->onFormSubmit($packet->formId, self::stupid_json_decode($packet->formData, true)); + if($packet->cancelReason !== null){ + //TODO: make APIs for this to allow plugins to use this information + return $this->player->onFormSubmit($packet->formId, null); + }elseif($packet->formData !== null){ + return $this->player->onFormSubmit($packet->formId, self::stupid_json_decode($packet->formData, true)); + }else{ + throw new PacketHandlingException("Expected either formData or cancelReason to be set in ModalFormResponsePacket"); + } } /** diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index 6b135c4c4..92e502351 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -99,9 +99,10 @@ class PreSpawnPacketHandler extends PacketHandler{ false, sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)), Uuid::fromString(Uuid::NIL), + false, [], 0, - GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries() + GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(), )); $this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers()); diff --git a/src/network/mcpe/serializer/ChunkSerializer.php b/src/network/mcpe/serializer/ChunkSerializer.php index 493dfc6ab..473ad615d 100644 --- a/src/network/mcpe/serializer/ChunkSerializer.php +++ b/src/network/mcpe/serializer/ChunkSerializer.php @@ -24,11 +24,8 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\serializer; use pocketmine\block\tile\Spawnable; -use pocketmine\block\tile\Tile; -use pocketmine\block\tile\TileFactory; use pocketmine\data\bedrock\BiomeIds; use pocketmine\data\bedrock\LegacyBiomeIdToStringIdMap; -use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\TreeRoot; use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; @@ -41,7 +38,6 @@ use pocketmine\world\format\PalettedBlockArray; use pocketmine\world\format\SubChunk; use function chr; use function count; -use function get_class; use function str_repeat; final class ChunkSerializer{ @@ -129,19 +125,9 @@ final class ChunkSerializer{ public static function serializeTiles(Chunk $chunk) : string{ $stream = new BinaryStream(); - $nbtSerializer = new NetworkNbtSerializer(); foreach($chunk->getTiles() as $tile){ if($tile instanceof Spawnable){ - //TODO: HACK! we send only the bare essentials to create a tile in the chunk itself, due to a bug in - //1.19.10 which causes items in tiles (item frames, lecterns) to not load properly when they are sent in - //a chunk via the classic chunk sending mechanism. We workaround this bug by sendingBlockActorDataPacket - //in NetworkSession to set the actual tile properties after sending the LevelChunkPacket. - $nbt = CompoundTag::create() - ->setString(Tile::TAG_ID, TileFactory::getInstance()->getSaveId(get_class($tile))) - ->setInt(Tile::TAG_X, $tile->getPosition()->getFloorX()) - ->setInt(Tile::TAG_Y, $tile->getPosition()->getFloorY()) - ->setInt(Tile::TAG_Z, $tile->getPosition()->getFloorZ()); - $stream->put($nbtSerializer->write(new TreeRoot($nbt))); + $stream->put($tile->getSerializedSpawnCompound()->getEncodedNbt()); } } diff --git a/src/utils/ReversePriorityQueue.php b/src/utils/ReversePriorityQueue.php index 76e648231..53fd0f08a 100644 --- a/src/utils/ReversePriorityQueue.php +++ b/src/utils/ReversePriorityQueue.php @@ -38,6 +38,7 @@ class ReversePriorityQueue extends \SplPriorityQueue{ * * @return int */ + #[\ReturnTypeWillChange] public function compare($priority1, $priority2){ //TODO: this will crash if non-numeric priorities are used return (int) -($priority1 - $priority2); diff --git a/tools/generate-permission-doc.php b/tools/generate-permission-doc.php index 09f9892a1..7d66740aa 100644 --- a/tools/generate-permission-doc.php +++ b/tools/generate-permission-doc.php @@ -35,20 +35,32 @@ use function fopen; use function fwrite; use function getcwd; use function ksort; +use function str_repeat; use function str_replace; +use function strlen; use function strtolower; use const SORT_STRING; use const STDERR; require dirname(__DIR__) . '/vendor/autoload.php'; +if(count($argv) > 2){ + fwrite(STDERR, "Required arguments: md|rst\n"); + exit(1); +} +$format = $argv[1] ?? "md"; +if($format !== "md" && $format !== "rst"){ + fwrite(STDERR, "Invalid format, expected either \"md\" or \"rst\"\n"); + exit(1); +} + function markdownify(string $name) : string{ return str_replace(['.', '`', ' '], ['', '', '-'], strtolower($name)); } DefaultPermissions::registerCorePermissions(); $cwd = Utils::assumeNotFalse(getcwd()); -$output = Path::join($cwd, "core-permissions.md"); +$output = Path::join($cwd, "core-permissions.$format"); echo "Writing output to $output\n"; $doc = fopen($output, "wb"); if($doc === false){ @@ -59,36 +71,92 @@ if($doc === false){ $permissions = PermissionManager::getInstance()->getPermissions(); ksort($permissions, SORT_STRING); -fwrite($doc, "# PocketMine-MP Core Permissions\n"); -fwrite($doc, "Generated from PocketMine-MP " . VersionInfo::VERSION()->getFullVersion() . "\n"); +$title = "List of " . VersionInfo::NAME . " core permissions"; +if($format === "md"){ + fwrite($doc, "# $title\n"); +}else{ + fwrite($doc, ".. _corepermissions:\n\n"); + fwrite($doc, "$title\n"); + fwrite($doc, str_repeat("=", strlen($title)) . "\n\n"); +} + +fwrite($doc, "Generated from " . VersionInfo::NAME . " " . VersionInfo::VERSION()->getFullVersion() . "\n"); fwrite($doc, "\n"); -fwrite($doc, "| Name | Description | Implied permissions |\n"); -fwrite($doc, "|:-----|:------------|:-------------------:|\n"); +if($format === "md"){ + fwrite($doc, "| Name | Description | Implied permissions |\n"); + fwrite($doc, "|:-----|:------------|:-------------------:|\n"); +}else{ + fwrite($doc, ".. list-table::\n"); + fwrite($doc, " :header-rows: 1\n\n"); + fwrite($doc, " * - Name\n"); + fwrite($doc, " - Description\n"); + fwrite($doc, " - Implied permissions\n"); + fwrite($doc, "\n"); +} foreach($permissions as $permission){ - $link = count($permission->getChildren()) === 0 ? "N/A" : "[Jump](#" . markdownify("Permissions implied by `" . $permission->getName() . "`") . ")"; - fwrite($doc, "| `" . $permission->getName() . "` | " . $permission->getDescription() . " | $link |\n"); + if($format === "md"){ + $link = count($permission->getChildren()) === 0 ? "N/A" : "[Jump](#" . markdownify("Permissions implied by `" . $permission->getName() . "`") . ")"; + fwrite($doc, "| `" . $permission->getName() . "` | " . $permission->getDescription() . " | $link |\n"); + }else{ + fwrite($doc, " * - ``" . $permission->getName() . "``\n"); + fwrite($doc, " - " . $permission->getDescription() . "\n"); + if(count($permission->getChildren()) === 0){ + fwrite($doc, " - N/A\n"); + }else{ + fwrite($doc, " - :ref:`JumpgetName() . ">`\n"); + } + } } fwrite($doc, "\n\n"); -fwrite($doc, "## Implied permissions\n"); -fwrite($doc, "Some permissions automatically grant (or deny) other permissions by default when granted. These are referred to as **implied permissions**.
\n"); -fwrite($doc, "Permissions may imply permissions which in turn imply other permissions (e.g. `pocketmine.group.operator` implies `pocketmine.group.user`, which in turn implies `pocketmine.command.help`).
\n"); -fwrite($doc, "Implied permissions can be overridden by explicit permissions from elsewhere.
\n"); -fwrite($doc, "**Note:** When explicitly denied, implied permissions are inverted. This means that \"granted\" becomes \"denied\" and vice versa.\n"); + +$title = "Implied permissions"; +if($format === "md"){ + fwrite($doc, "## $title\n"); +}else{ + fwrite($doc, "$title\n"); + fwrite($doc, str_repeat("-", strlen($title)) . "\n\n"); +} +$newline = $format === "md" ? "
\n" : "\n\n"; +$code = $format === "md" ? "`" : "``"; +fwrite($doc, "Some permissions automatically grant (or deny) other permissions by default when granted. These are referred to as **implied permissions**.$newline"); +fwrite($doc, "Permissions may imply permissions which in turn imply other permissions (e.g. {$code}pocketmine.group.operator{$code} implies {$code}pocketmine.group.user{$code}, which in turn implies {$code}pocketmine.command.help{$code}).$newline"); +fwrite($doc, "Implied permissions can be overridden by explicit permissions from elsewhere.$newline"); +fwrite($doc, "**Note:** When explicitly denied, implied permissions are inverted. This means that \"granted\" becomes \"denied\" and vice versa.$newline"); fwrite($doc, "\n\n"); foreach($permissions as $permission){ if(count($permission->getChildren()) === 0){ continue; } - fwrite($doc, "### Permissions implied by `" . $permission->getName() . "`\n"); + $title = "Permissions implied by " . $code . $permission->getName() . $code; + if($format === "md"){ + fwrite($doc, "### $title\n"); + }else{ + fwrite($doc, ".. _permissions_implied_by_" . $permission->getName() . ":\n\n"); + fwrite($doc, "$title\n"); + fwrite($doc, str_repeat("~", strlen($title)) . "\n\n"); + } fwrite($doc, "Users granted this permission will also be granted/denied the following permissions implicitly:\n\n"); - fwrite($doc, "| Name | Type |\n"); - fwrite($doc, "|:-----|:----:|\n"); - $children = $permission->getChildren(); - ksort($children, SORT_STRING); - foreach(Utils::stringifyKeys($children) as $childName => $isGranted){ - fwrite($doc, "| `$childName` | " . ($isGranted ? "Granted" : "Denied") . " |\n"); + if($format === "md"){ + fwrite($doc, "| Name | Type |\n"); + fwrite($doc, "|:-----|:----:|\n"); + $children = $permission->getChildren(); + ksort($children, SORT_STRING); + foreach(Utils::stringifyKeys($children) as $childName => $isGranted){ + fwrite($doc, "| `$childName` | " . ($isGranted ? "Granted" : "Denied") . " |\n"); + } + }else{ + fwrite($doc, ".. list-table::\n"); + fwrite($doc, " :header-rows: 1\n\n"); + fwrite($doc, " * - Name\n"); + fwrite($doc, " - Type\n"); + $children = $permission->getChildren(); + ksort($children, SORT_STRING); + foreach(Utils::stringifyKeys($children) as $childName => $isGranted){ + fwrite($doc, " * - ``$childName``\n"); + fwrite($doc, " - " . ($isGranted ? "Granted" : "Denied") . "\n"); + } } fwrite($doc, "\n"); }