Merge branch 'minor-next' into trees

This commit is contained in:
Dylan K. Taylor 2023-07-01 12:25:44 +01:00
commit 5d51ffdfe3
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
46 changed files with 770 additions and 310 deletions

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
.github/readme/pocketmine-dark-rgb.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

BIN
.github/readme/pocketmine-rgb.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

View File

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

View File

@ -4,8 +4,8 @@
<img src="https://github.com/pmmp/PocketMine-MP/blob/stable/.github/readme/pocketmine.png" alt="The PocketMine-MP logo" title="PocketMine" loading="eager" />
<![endif]-->
<picture>
<source srcset="https://github.com/pmmp/PocketMine-MP/raw/stable/.github/readme/pocketmine-dark.png" media="(prefers-color-scheme: dark)">
<img src="https://github.com/pmmp/PocketMine-MP/raw/stable/.github/readme/pocketmine.png" loading="eager" />
<source srcset="https://raw.githubusercontent.com/pmmp/PocketMine-MP/stable/.github/readme/pocketmine-dark-rgb.gif" media="(prefers-color-scheme: dark)">
<img src="https://raw.githubusercontent.com/pmmp/PocketMine-MP/stable/.github/readme/pocketmine-rgb.gif" loading="eager" />
</picture>
</a><br>
<b>A highly customisable, open source server software for Minecraft: Bedrock Edition written in PHP</b>

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\build\generate_item_serializer_ids;
use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\network\mcpe\convert\ItemTypeDictionaryFromDataHelper;
use pocketmine\network\mcpe\protocol\serializer\ItemTypeDictionary;
@ -45,10 +46,10 @@ function constifyMcId(string $id) : string{
return strtoupper(explode(":", $id, 2)[1]);
}
function generateItemIds(ItemTypeDictionary $dictionary) : void{
function generateItemIds(ItemTypeDictionary $dictionary, BlockItemIdMap $blockItemIdMap) : void{
$ids = [];
foreach($dictionary->getEntries() as $entry){
if($entry->getNumericId() < 256){ //blockitems are serialized via BlockStateSerializer
if($entry->getStringId() === "minecraft:air" || $blockItemIdMap->lookupBlockId($entry->getStringId()) !== null){ //blockitems are serialized via BlockStateSerializer
continue;
}
$ids[$entry->getStringId()] = $entry->getStringId();
@ -92,6 +93,7 @@ if($raw === false){
}
$dictionary = ItemTypeDictionaryFromDataHelper::loadFromString($raw);
generateItemIds($dictionary);
$blockItemIdMap = BlockItemIdMap::getInstance();
generateItemIds($dictionary, $blockItemIdMap);
echo "Done. Don't forget to run CS fixup after generating code.\n";

@ -1 +1 @@
Subproject commit fcbc15f23e70d01b618cf21d090146e02254e735
Subproject commit 2a21c579007a8fd7244f9faad498cd1c8c33004c

View File

@ -27,8 +27,22 @@ If you're upgrading from 4.20.x directly to 4.22.x, please also read the followi
Released 9th June 2023.
## Fixes
- Reokaced workaround for an old teleporting client bug:
- Replaced workaround for an old teleporting client bug:
- This workaround broke due to an additional client bug introduced by 1.20, causing players to become frozen to observers when teleported.
- The original client bug has still not been fixed, meaning a new workaround was needed, but no perfect solution could be found.
- The new workaround involves broadcasting teleport movements as regular movements, which causes unwanted interpolation between the old and new positions, but otherwise works correctly. This solution is not ideal, but it is the best we can do for now.
- See issues [#4394](https://github.com/pmmp/PocketMine-MP/issues/4394) and [#5810](https://github.com/pmmp/PocketMine-MP/issues/5810) for more details.
# 4.22.2
Released 1st July 2023.
## Changes
- Added obsoletion warnings to the server log at the end of the startup sequence.
## Fixes
- Fixed players being disconnected en masse with "Not authenticated" messages.
- This occurred due to a check intended to disable the old authentication key after July 1st.
- We expected that the new key would have been deployed by Mojang by now, but it seems like that has not yet happened.
- Due to the lack of a hard date for the key changeover, we guessed that July 1st would be a safe bet, but this appears to have backfired.
- This version will accept both old and new keys indefinitely.
- A security release will be published to remove the old key after the transition occurs.

View File

@ -28,3 +28,25 @@ Released 9th June 2023.
- [4.22.1](https://github.com/pmmp/PocketMine-MP/blob/4.22.1/changelogs/4.22.md#4221) - Teleportation client bug workarounds
This release contains no other changes.
# 5.1.3
Released 1st July 2023.
**This release includes changes from the following releases:**
- [4.22.2](https://github.com/pmmp/PocketMine-MP/blob/4.22.2/changelogs/4.22.md#4222) - Authentication time bomb fix
## General
- Updated logos to new RGB-style logo. Thanks to @MrCakeSlayer and @HBIDamian for their efforts.
- Improved error messages generated by the world system when some version tags are missing from `level.dat` in Bedrock worlds.
- Outsourced Composer dependencies now only receive patch updates automatically (pinned using the `~` constraint).
- Minor and major updates now require manually updating `composer.json`, to ensure that the plugin API is not broken by libraries getting randomly updated from one patch release to the next.
## Documentation
- Updated doc comment for `Player->setGamemode()` to remove outdated information.
- Added documentation for the `$clickVector` parameter of `Block->onInteract()` to specify that it is relative to the block's position.
- Added missing `@required` tag for `BlockStateUpgradeSchemaModelBlockRemap->newState`.
## Fixes
- Fixed blue candles not appearing in the creative inventory.
- Fixed server crash when block-picking candle cakes.
- `World->useItemOn()` now ensures that the `$clickVector` components are always in the range of 0-1. Previously, any invalid values were accepted, potentially leading to a crash.

View File

@ -22,7 +22,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pmmpthread": "^6.0.1",
"ext-pmmpthread": "^6.0.4",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
@ -31,8 +31,8 @@
"ext-zip": "*",
"ext-zlib": ">=1.2.11",
"composer-runtime-api": "^2.0",
"adhocore/json-comment": "^1.1",
"fgrosse/phpasn1": "^2.3",
"adhocore/json-comment": "~1.2.0",
"fgrosse/phpasn1": "~2.5.0",
"pocketmine/netresearch-jsonmapper": "~v4.2.999",
"pocketmine/bedrock-block-upgrade-schema": "~2.2.0+bedrock-1.20.0",
"pocketmine/bedrock-data": "~2.3.0+bedrock-1.20.0",
@ -49,8 +49,8 @@
"pocketmine/raklib": "^0.15.0",
"pocketmine/raklib-ipc": "^0.2.0",
"pocketmine/snooze": "^0.5.0",
"ramsey/uuid": "^4.1",
"symfony/filesystem": "^6.2"
"ramsey/uuid": "~4.7.0",
"symfony/filesystem": "~6.2.0"
},
"require-dev": {
"phpstan/phpstan": "1.10.16",

50
composer.lock generated
View File

@ -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": "5483e35c09044ab9d989cec8fdf4a399",
"content-hash": "3b102702eabc31fd8b2651db6c259397",
"packages": [
{
"name": "adhocore/json-comment",
@ -224,16 +224,16 @@
},
{
"name": "pocketmine/bedrock-data",
"version": "2.3.0+bedrock-1.20.0",
"version": "2.3.1+bedrock-1.20.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockData.git",
"reference": "b3dd3f4b8e3b6759c5d84de6ec85bb20b668c3a9"
"reference": "fb89ccdc039252462d8d068a769635e24151b7e2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/b3dd3f4b8e3b6759c5d84de6ec85bb20b668c3a9",
"reference": "b3dd3f4b8e3b6759c5d84de6ec85bb20b668c3a9",
"url": "https://api.github.com/repos/pmmp/BedrockData/zipball/fb89ccdc039252462d8d068a769635e24151b7e2",
"reference": "fb89ccdc039252462d8d068a769635e24151b7e2",
"shasum": ""
},
"type": "library",
@ -244,9 +244,9 @@
"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.20.0"
"source": "https://github.com/pmmp/BedrockData/tree/2.3.1+bedrock-1.20.0"
},
"time": "2023-06-07T19:06:47+00:00"
"time": "2023-06-13T16:42:09+00:00"
},
{
"name": "pocketmine/bedrock-item-upgrade-schema",
@ -998,16 +998,16 @@
},
{
"name": "symfony/filesystem",
"version": "v6.2.10",
"version": "v6.2.12",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894"
"reference": "b0818e7203e53540f2a5c9a5017d97897df1e9bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/fd588debf7d1bc16a2c84b4b3b71145d9946b894",
"reference": "fd588debf7d1bc16a2c84b4b3b71145d9946b894",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/b0818e7203e53540f2a5c9a5017d97897df1e9bb",
"reference": "b0818e7203e53540f2a5c9a5017d97897df1e9bb",
"shasum": ""
},
"require": {
@ -1041,7 +1041,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v6.2.10"
"source": "https://github.com/symfony/filesystem/tree/v6.2.12"
},
"funding": [
{
@ -1057,7 +1057,7 @@
"type": "tidelift"
}
],
"time": "2023-04-18T13:46:08+00:00"
"time": "2023-06-01T08:29:37+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -1287,16 +1287,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.5",
"version": "v4.16.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e"
"reference": "19526a33fb561ef417e822e85f08a00db4059c17"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
"reference": "19526a33fb561ef417e822e85f08a00db4059c17",
"shasum": ""
},
"require": {
@ -1337,9 +1337,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
},
"time": "2023-05-19T20:20:00+00:00"
"time": "2023-06-25T14:52:30+00:00"
},
{
"name": "phar-io/manifest",
@ -1937,16 +1937,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.2.1",
"version": "10.2.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "599b33294350e8f51163119d5670512f98b0490d"
"reference": "35c8cac1734ede2ae354a6644f7088356ff5b08e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/599b33294350e8f51163119d5670512f98b0490d",
"reference": "599b33294350e8f51163119d5670512f98b0490d",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/35c8cac1734ede2ae354a6644f7088356ff5b08e",
"reference": "35c8cac1734ede2ae354a6644f7088356ff5b08e",
"shasum": ""
},
"require": {
@ -2018,7 +2018,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.1"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.3"
},
"funding": [
{
@ -2034,7 +2034,7 @@
"type": "tidelift"
}
],
"time": "2023-06-05T05:15:51+00:00"
"time": "2023-06-30T06:17:38+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@ -120,8 +120,8 @@ namespace pocketmine {
}
if(($pmmpthread_version = phpversion("pmmpthread")) !== false){
if(version_compare($pmmpthread_version, "6.0.1") < 0 || version_compare($pmmpthread_version, "7.0.0") >= 0){
$messages[] = "pmmpthread ^6.0.1 is required, while you have $pmmpthread_version.";
if(version_compare($pmmpthread_version, "6.0.4") < 0 || version_compare($pmmpthread_version, "7.0.0") >= 0){
$messages[] = "pmmpthread ^6.0.4 is required, while you have $pmmpthread_version.";
}
}

View File

@ -467,7 +467,8 @@ class Block{
/**
* Do actions when interacted by Item. Returns if it has done anything
*
* @param Item[] &$returnedItems Items to be added to the target's inventory (or dropped, if the inventory is full)
* @param Vector3 $clickVector Exact position where the click occurred, relative to the block's integer position
* @param Item[] &$returnedItems Items to be added to the target's inventory (or dropped, if the inventory is full)
*/
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
return false;

View File

@ -64,7 +64,7 @@ class CakeWithCandle extends BaseCake{
}
public function getPickedItem(bool $addUserData = false) : Item{
return VanillaBlocks::CAKE()->getPickedItem($addUserData);
return VanillaBlocks::CAKE()->asItem();
}
public function getResidue() : Block{

View File

@ -33,6 +33,7 @@ use pocketmine\utils\EnumTrait;
*
* @method static MobHeadType CREEPER()
* @method static MobHeadType DRAGON()
* @method static MobHeadType PIGLIN()
* @method static MobHeadType PLAYER()
* @method static MobHeadType SKELETON()
* @method static MobHeadType WITHER_SKELETON()
@ -50,7 +51,8 @@ final class MobHeadType{
new MobHeadType("zombie", "Zombie Head"),
new MobHeadType("player", "Player Head"),
new MobHeadType("creeper", "Creeper Head"),
new MobHeadType("dragon", "Dragon Head")
new MobHeadType("dragon", "Dragon Head"),
new MobHeadType("piglin", "Piglin Head")
);
}

View File

@ -48,6 +48,7 @@ final class MobHeadTypeIdMap{
$this->register(3, MobHeadType::PLAYER());
$this->register(4, MobHeadType::CREEPER());
$this->register(5, MobHeadType::DRAGON());
$this->register(6, MobHeadType::PIGLIN());
}
private function register(int $id, MobHeadType $type) : void{

View File

@ -24,6 +24,9 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock\block\upgrade;
use pocketmine\nbt\tag\Tag;
use pocketmine\utils\Utils;
use function array_diff;
use function count;
final class BlockStateUpgradeSchemaBlockRemap{
/**
@ -37,8 +40,43 @@ final class BlockStateUpgradeSchemaBlockRemap{
*/
public function __construct(
public array $oldState,
public string $newName,
public string|BlockStateUpgradeSchemaFlattenedName $newName,
public array $newState,
public array $copiedState
){}
public function equals(self $that) : bool{
$sameName = $this->newName === $that->newName ||
(
$this->newName instanceof BlockStateUpgradeSchemaFlattenedName &&
$that->newName instanceof BlockStateUpgradeSchemaFlattenedName &&
$this->newName->equals($that->newName)
);
if(!$sameName){
return false;
}
if(
count($this->oldState) !== count($that->oldState) ||
count($this->newState) !== count($that->newState) ||
count($this->copiedState) !== count($that->copiedState) ||
count(array_diff($this->copiedState, $that->copiedState)) !== 0
){
return false;
}
foreach(Utils::stringifyKeys($this->oldState) as $propertyName => $propertyValue){
if(!isset($that->oldState[$propertyName]) || !$that->oldState[$propertyName]->equals($propertyValue)){
//different filter value
return false;
}
}
foreach(Utils::stringifyKeys($this->newState) as $propertyName => $propertyValue){
if(!isset($that->newState[$propertyName]) || !$that->newState[$propertyName]->equals($propertyValue)){
//different replacement value
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\data\bedrock\block\upgrade;
final class BlockStateUpgradeSchemaFlattenedName{
public function __construct(
public string $prefix,
public string $flattenedProperty,
public string $suffix
){}
public function equals(self $that) : bool{
return $this->prefix === $that->prefix &&
$this->flattenedProperty === $that->flattenedProperty &&
$this->suffix === $that->suffix;
}
}

View File

@ -25,6 +25,7 @@ namespace pocketmine\data\bedrock\block\upgrade;
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModel;
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelBlockRemap;
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelFlattenedName;
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelTag;
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelValueRemap;
use pocketmine\nbt\tag\ByteTag;
@ -43,12 +44,14 @@ use function get_debug_type;
use function gettype;
use function implode;
use function is_object;
use function is_string;
use function json_decode;
use function json_encode;
use function ksort;
use function sort;
use function str_pad;
use function strval;
use function usort;
use const JSON_THROW_ON_ERROR;
use const SORT_NUMERIC;
use const STR_PAD_LEFT;
@ -154,9 +157,17 @@ final class BlockStateUpgradeSchemaUtils{
foreach(Utils::stringifyKeys($model->remappedStates ?? []) as $oldBlockName => $remaps){
foreach($remaps as $remap){
if(isset($remap->newName) === isset($remap->newFlattenedName)){
throw new \UnexpectedValueException("Expected exactly one of 'newName' or 'newFlattenedName' properties to be set");
}
$result->remappedStates[$oldBlockName][] = new BlockStateUpgradeSchemaBlockRemap(
array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->oldState ?? []),
$remap->newName,
$remap->newName ?? new BlockStateUpgradeSchemaFlattenedName(
$remap->newFlattenedName->prefix,
$remap->newFlattenedName->flattenedProperty,
$remap->newFlattenedName->suffix
),
array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->newState ?? []),
$remap->copiedState ?? []
);
@ -285,7 +296,13 @@ final class BlockStateUpgradeSchemaUtils{
foreach($remaps as $remap){
$modelRemap = new BlockStateUpgradeSchemaModelBlockRemap(
array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->oldState),
$remap->newName,
is_string($remap->newName) ?
$remap->newName :
new BlockStateUpgradeSchemaModelFlattenedName(
$remap->newName->prefix,
$remap->newName->flattenedProperty,
$remap->newName->suffix
),
array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->newState),
$remap->copiedState
);
@ -299,7 +316,15 @@ final class BlockStateUpgradeSchemaUtils{
}
$keyedRemaps[$key] = $modelRemap;
}
ksort($keyedRemaps);
usort($keyedRemaps, function(BlockStateUpgradeSchemaModelBlockRemap $a, BlockStateUpgradeSchemaModelBlockRemap $b) : int{
//remaps with more specific criteria must come first
$filterSizeCompare = count($b->oldState ?? []) <=> count($a->oldState ?? []);
if($filterSizeCompare !== 0){
return $filterSizeCompare;
}
//remaps with the same number of criteria should be sorted alphabetically, but this is not strictly necessary
return json_encode($a->oldState ?? []) <=> json_encode($b->oldState ?? []);
});
$result->remappedStates[$oldBlockName] = array_values($keyedRemaps);
}
if(isset($result->remappedStates)){

View File

@ -24,11 +24,14 @@ declare(strict_types=1);
namespace pocketmine\data\bedrock\block\upgrade;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\tag\Tag;
use pocketmine\utils\Utils;
use function count;
use function is_string;
use function ksort;
use function max;
use function sprintf;
use const SORT_NUMERIC;
final class BlockStateUpgrader{
@ -79,6 +82,20 @@ final class BlockStateUpgrader{
continue 2; //try next state
}
}
if(is_string($remap->newName)){
$newName = $remap->newName;
}else{
$flattenedValue = $oldState[$remap->newName->flattenedProperty];
if($flattenedValue instanceof StringTag){
$newName = sprintf("%s%s%s", $remap->newName->prefix, $flattenedValue->getValue(), $remap->newName->suffix);
unset($oldState[$remap->newName->flattenedProperty]);
}else{
//flattened property is not a TAG_String, so this transformation is not applicable
continue;
}
}
$newState = $remap->newState;
foreach($remap->copiedState as $stateName){
if(isset($oldState[$stateName])){
@ -86,7 +103,7 @@ final class BlockStateUpgrader{
}
}
$blockStateData = new BlockStateData($remap->newName, $newState, $resultVersion);
$blockStateData = new BlockStateData($newName, $newState, $resultVersion);
continue 2; //try next schema
}
}

View File

@ -34,12 +34,21 @@ final class BlockStateUpgradeSchemaModelBlockRemap{
*/
public ?array $oldState;
/** @required */
/**
* Either this or newFlattenedName must be present
* Due to technical limitations of jsonmapper, we can't use a union type here
*/
public string $newName;
/**
* Either this or newName must be present
* Due to technical limitations of jsonmapper, we can't use a union type here
*/
public BlockStateUpgradeSchemaModelFlattenedName $newFlattenedName;
/**
* @var BlockStateUpgradeSchemaModelTag[]|null
* @phpstan-var array<string, BlockStateUpgradeSchemaModelTag>|null
* @required
*/
public ?array $newState;
@ -58,9 +67,13 @@ final class BlockStateUpgradeSchemaModelBlockRemap{
* @phpstan-param array<string, BlockStateUpgradeSchemaModelTag> $newState
* @phpstan-param list<string> $copiedState
*/
public function __construct(array $oldState, string $newName, array $newState, array $copiedState){
public function __construct(array $oldState, string|BlockStateUpgradeSchemaModelFlattenedName $newNameRule, array $newState, array $copiedState){
$this->oldState = count($oldState) === 0 ? null : $oldState;
$this->newName = $newName;
if($newNameRule instanceof BlockStateUpgradeSchemaModelFlattenedName){
$this->newFlattenedName = $newNameRule;
}else{
$this->newName = $newNameRule;
}
$this->newState = count($newState) === 0 ? null : $newState;
$this->copiedState = $copiedState;
}

View File

@ -0,0 +1,40 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\data\bedrock\block\upgrade\model;
final class BlockStateUpgradeSchemaModelFlattenedName{
/** @required */
public string $prefix;
/** @required */
public string $flattenedProperty;
/** @required */
public string $suffix;
public function __construct(string $prefix, string $flattenedProperty, string $suffix){
$this->prefix = $prefix;
$this->flattenedProperty = $flattenedProperty;
$this->suffix = $suffix;
}
}

View File

@ -177,6 +177,7 @@ final class ItemSerializer{
throw new ItemTypeSerializeException($e->getMessage(), 0, $e);
}
//TODO: this really ought to throw if there's no blockitem ID
$itemNameId = BlockItemIdMap::getInstance()->lookupItemId($blockStateData->getName()) ?? $blockStateData->getName();
return new Data($itemNameId, 0, $blockStateData);

View File

@ -135,6 +135,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Block(Ids::CAKE, Blocks::CAKE());
$this->map1to1Block(Ids::CAULDRON, Blocks::CAULDRON());
$this->map1to1Block(Ids::CHAIN, Blocks::CHAIN());
$this->map1to1Block(Ids::CHERRY_DOOR, Blocks::CHERRY_DOOR());
$this->map1to1Block(Ids::COMPARATOR, Blocks::REDSTONE_COMPARATOR());
$this->map1to1Block(Ids::CRIMSON_DOOR, Blocks::CRIMSON_DOOR());
$this->map1to1Block(Ids::DARK_OAK_DOOR, Blocks::DARK_OAK_DOOR());

View File

@ -30,6 +30,7 @@ final class ItemTypeNames{
public const ACACIA_BOAT = "minecraft:acacia_boat";
public const ACACIA_CHEST_BOAT = "minecraft:acacia_chest_boat";
public const ACACIA_DOOR = "minecraft:acacia_door";
public const ACACIA_HANGING_SIGN = "minecraft:acacia_hanging_sign";
public const ACACIA_SIGN = "minecraft:acacia_sign";
public const AGENT_SPAWN_EGG = "minecraft:agent_spawn_egg";
public const ALLAY_SPAWN_EGG = "minecraft:allay_spawn_egg";
@ -45,6 +46,8 @@ final class ItemTypeNames{
public const BAKED_POTATO = "minecraft:baked_potato";
public const BALLOON = "minecraft:balloon";
public const BAMBOO_CHEST_RAFT = "minecraft:bamboo_chest_raft";
public const BAMBOO_DOOR = "minecraft:bamboo_door";
public const BAMBOO_HANGING_SIGN = "minecraft:bamboo_hanging_sign";
public const BAMBOO_RAFT = "minecraft:bamboo_raft";
public const BAMBOO_SIGN = "minecraft:bamboo_sign";
public const BANNER = "minecraft:banner";
@ -59,6 +62,7 @@ final class ItemTypeNames{
public const BIRCH_BOAT = "minecraft:birch_boat";
public const BIRCH_CHEST_BOAT = "minecraft:birch_chest_boat";
public const BIRCH_DOOR = "minecraft:birch_door";
public const BIRCH_HANGING_SIGN = "minecraft:birch_hanging_sign";
public const BIRCH_SIGN = "minecraft:birch_sign";
public const BLACK_DYE = "minecraft:black_dye";
public const BLADE_POTTERY_SHERD = "minecraft:blade_pottery_sherd";
@ -100,6 +104,8 @@ final class ItemTypeNames{
public const CHARCOAL = "minecraft:charcoal";
public const CHERRY_BOAT = "minecraft:cherry_boat";
public const CHERRY_CHEST_BOAT = "minecraft:cherry_chest_boat";
public const CHERRY_DOOR = "minecraft:cherry_door";
public const CHERRY_HANGING_SIGN = "minecraft:cherry_hanging_sign";
public const CHERRY_SIGN = "minecraft:cherry_sign";
public const CHEST_BOAT = "minecraft:chest_boat";
public const CHEST_MINECART = "minecraft:chest_minecart";
@ -132,6 +138,7 @@ final class ItemTypeNames{
public const CREEPER_BANNER_PATTERN = "minecraft:creeper_banner_pattern";
public const CREEPER_SPAWN_EGG = "minecraft:creeper_spawn_egg";
public const CRIMSON_DOOR = "minecraft:crimson_door";
public const CRIMSON_HANGING_SIGN = "minecraft:crimson_hanging_sign";
public const CRIMSON_SIGN = "minecraft:crimson_sign";
public const CROSSBOW = "minecraft:crossbow";
public const CYAN_DYE = "minecraft:cyan_dye";
@ -139,6 +146,7 @@ final class ItemTypeNames{
public const DARK_OAK_BOAT = "minecraft:dark_oak_boat";
public const DARK_OAK_CHEST_BOAT = "minecraft:dark_oak_chest_boat";
public const DARK_OAK_DOOR = "minecraft:dark_oak_door";
public const DARK_OAK_HANGING_SIGN = "minecraft:dark_oak_hanging_sign";
public const DARK_OAK_SIGN = "minecraft:dark_oak_sign";
public const DIAMOND = "minecraft:diamond";
public const DIAMOND_AXE = "minecraft:diamond_axe";
@ -256,6 +264,7 @@ final class ItemTypeNames{
public const JUNGLE_BOAT = "minecraft:jungle_boat";
public const JUNGLE_CHEST_BOAT = "minecraft:jungle_chest_boat";
public const JUNGLE_DOOR = "minecraft:jungle_door";
public const JUNGLE_HANGING_SIGN = "minecraft:jungle_hanging_sign";
public const JUNGLE_SIGN = "minecraft:jungle_sign";
public const KELP = "minecraft:kelp";
public const LAPIS_LAZULI = "minecraft:lapis_lazuli";
@ -281,6 +290,7 @@ final class ItemTypeNames{
public const MANGROVE_BOAT = "minecraft:mangrove_boat";
public const MANGROVE_CHEST_BOAT = "minecraft:mangrove_chest_boat";
public const MANGROVE_DOOR = "minecraft:mangrove_door";
public const MANGROVE_HANGING_SIGN = "minecraft:mangrove_hanging_sign";
public const MANGROVE_SIGN = "minecraft:mangrove_sign";
public const MEDICINE = "minecraft:medicine";
public const MELON_SEEDS = "minecraft:melon_seeds";
@ -331,6 +341,7 @@ final class ItemTypeNames{
public const NPC_SPAWN_EGG = "minecraft:npc_spawn_egg";
public const OAK_BOAT = "minecraft:oak_boat";
public const OAK_CHEST_BOAT = "minecraft:oak_chest_boat";
public const OAK_HANGING_SIGN = "minecraft:oak_hanging_sign";
public const OAK_SIGN = "minecraft:oak_sign";
public const OCELOT_SPAWN_EGG = "minecraft:ocelot_spawn_egg";
public const ORANGE_DYE = "minecraft:orange_dye";
@ -420,6 +431,7 @@ final class ItemTypeNames{
public const SPRUCE_BOAT = "minecraft:spruce_boat";
public const SPRUCE_CHEST_BOAT = "minecraft:spruce_chest_boat";
public const SPRUCE_DOOR = "minecraft:spruce_door";
public const SPRUCE_HANGING_SIGN = "minecraft:spruce_hanging_sign";
public const SPRUCE_SIGN = "minecraft:spruce_sign";
public const SPYGLASS = "minecraft:spyglass";
public const SQUID_SPAWN_EGG = "minecraft:squid_spawn_egg";
@ -458,6 +470,7 @@ final class ItemTypeNames{
public const WARDEN_SPAWN_EGG = "minecraft:warden_spawn_egg";
public const WARPED_DOOR = "minecraft:warped_door";
public const WARPED_FUNGUS_ON_A_STICK = "minecraft:warped_fungus_on_a_stick";
public const WARPED_HANGING_SIGN = "minecraft:warped_hanging_sign";
public const WARPED_SIGN = "minecraft:warped_sign";
public const WATER_BUCKET = "minecraft:water_bucket";
public const WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:wayfinder_armor_trim_smithing_template";

View File

@ -130,10 +130,11 @@ trait RuntimeEnumDeserializerTrait{
$value = match($this->readInt(3)){
0 => \pocketmine\block\utils\MobHeadType::CREEPER(),
1 => \pocketmine\block\utils\MobHeadType::DRAGON(),
2 => \pocketmine\block\utils\MobHeadType::PLAYER(),
3 => \pocketmine\block\utils\MobHeadType::SKELETON(),
4 => \pocketmine\block\utils\MobHeadType::WITHER_SKELETON(),
5 => \pocketmine\block\utils\MobHeadType::ZOMBIE(),
2 => \pocketmine\block\utils\MobHeadType::PIGLIN(),
3 => \pocketmine\block\utils\MobHeadType::PLAYER(),
4 => \pocketmine\block\utils\MobHeadType::SKELETON(),
5 => \pocketmine\block\utils\MobHeadType::WITHER_SKELETON(),
6 => \pocketmine\block\utils\MobHeadType::ZOMBIE(),
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for MobHeadType")
};
}

View File

@ -130,10 +130,11 @@ trait RuntimeEnumSerializerTrait{
$this->writeInt(3, match($value){
\pocketmine\block\utils\MobHeadType::CREEPER() => 0,
\pocketmine\block\utils\MobHeadType::DRAGON() => 1,
\pocketmine\block\utils\MobHeadType::PLAYER() => 2,
\pocketmine\block\utils\MobHeadType::SKELETON() => 3,
\pocketmine\block\utils\MobHeadType::WITHER_SKELETON() => 4,
\pocketmine\block\utils\MobHeadType::ZOMBIE() => 5,
\pocketmine\block\utils\MobHeadType::PIGLIN() => 2,
\pocketmine\block\utils\MobHeadType::PLAYER() => 3,
\pocketmine\block\utils\MobHeadType::SKELETON() => 4,
\pocketmine\block\utils\MobHeadType::WITHER_SKELETON() => 5,
\pocketmine\block\utils\MobHeadType::ZOMBIE() => 6,
default => throw new \pocketmine\utils\AssumptionFailedError("All MobHeadType cases should be covered")
});
}

View File

@ -43,7 +43,12 @@ class BlockPlaceEvent extends Event implements Cancellable{
protected BlockTransaction $transaction,
protected Block $blockAgainst,
protected Item $item
){}
){
$world = $this->blockAgainst->getPosition()->getWorld();
foreach($this->transaction->getBlocks() as [$x, $y, $z, $block]){
$block->position($world, $x, $y, $z);
}
}
/**
* Returns the player who is placing the block.

View File

@ -208,6 +208,19 @@ final class StringToItemParser extends StringToTParser{
$result->registerBlock("cauldron", fn() => Blocks::CAULDRON());
$result->registerBlock("cave_vines", fn() => Blocks::CAVE_VINES());
$result->registerBlock("chain", fn() => Blocks::CHAIN());
$result->registerBlock("cherry_button", fn() => Blocks::CHERRY_BUTTON());
$result->registerBlock("cherry_door", fn() => Blocks::CHERRY_DOOR());
$result->registerBlock("cherry_fence", fn() => Blocks::CHERRY_FENCE());
$result->registerBlock("cherry_fence_gate", fn() => Blocks::CHERRY_FENCE_GATE());
$result->registerBlock("cherry_leaves", fn() => Blocks::CHERRY_LEAVES());
$result->registerBlock("cherry_log", fn() => Blocks::CHERRY_LOG());
$result->registerBlock("cherry_planks", fn() => Blocks::CHERRY_PLANKS());
$result->registerBlock("cherry_pressure_plate", fn() => Blocks::CHERRY_PRESSURE_PLATE());
$result->registerBlock("cherry_sign", fn() => Blocks::CHERRY_SIGN());
$result->registerBlock("cherry_slab", fn() => Blocks::CHERRY_SLAB());
$result->registerBlock("cherry_stairs", fn() => Blocks::CHERRY_STAIRS());
$result->registerBlock("cherry_trapdoor", fn() => Blocks::CHERRY_TRAPDOOR());
$result->registerBlock("cherry_wood", fn() => Blocks::CHERRY_WOOD());
$result->registerBlock("chemical_heat", fn() => Blocks::CHEMICAL_HEAT());
$result->registerBlock("chemistry_table", fn() => Blocks::COMPOUND_CREATOR());
$result->registerBlock("chest", fn() => Blocks::CHEST());
@ -843,6 +856,7 @@ final class StringToItemParser extends StringToTParser{
$result->registerBlock("packed_mud", fn() => Blocks::PACKED_MUD());
$result->registerBlock("peony", fn() => Blocks::PEONY());
$result->registerBlock("pink_tulip", fn() => Blocks::PINK_TULIP());
$result->registerBlock("piglin_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::PIGLIN()));
$result->registerBlock("plank", fn() => Blocks::OAK_PLANKS());
$result->registerBlock("planks", fn() => Blocks::OAK_PLANKS());
$result->registerBlock("player_head", fn() => Blocks::MOB_HEAD()->setMobHeadType(MobHeadType::PLAYER()));
@ -1065,6 +1079,7 @@ final class StringToItemParser extends StringToTParser{
$result->registerBlock("trunk2", fn() => Blocks::ACACIA_LOG()->setStripped(false));
$result->registerBlock("tuff", fn() => Blocks::TUFF());
$result->registerBlock("twisting_vines", fn() => Blocks::TWISTING_VINES());
$result->registerBlock("underwater_tnt", fn() => Blocks::TNT()->setWorksUnderwater(true));
$result->registerBlock("underwater_torch", fn() => Blocks::UNDERWATER_TORCH());
$result->registerBlock("undyed_shulker_box", fn() => Blocks::SHULKER_BOX());
$result->registerBlock("unlit_redstone_torch", fn() => Blocks::REDSTONE_TORCH());

View File

@ -39,7 +39,6 @@ use pocketmine\world\format\io\FastChunkSerializer;
class ChunkRequestTask extends AsyncTask{
private const TLS_KEY_PROMISE = "promise";
private const TLS_KEY_ERROR_HOOK = "errorHook";
protected string $chunk;
protected int $chunkX;
@ -48,10 +47,7 @@ class ChunkRequestTask extends AsyncTask{
protected NonThreadSafeValue $compressor;
private string $tiles;
/**
* @phpstan-param (\Closure() : void)|null $onError
*/
public function __construct(int $chunkX, int $chunkZ, Chunk $chunk, CompressBatchPromise $promise, Compressor $compressor, ?\Closure $onError = null){
public function __construct(int $chunkX, int $chunkZ, Chunk $chunk, CompressBatchPromise $promise, Compressor $compressor){
$this->compressor = new NonThreadSafeValue($compressor);
$this->chunk = FastChunkSerializer::serializeTerrain($chunk);
@ -60,7 +56,6 @@ class ChunkRequestTask extends AsyncTask{
$this->tiles = ChunkSerializer::serializeTiles($chunk);
$this->storeLocal(self::TLS_KEY_PROMISE, $promise);
$this->storeLocal(self::TLS_KEY_ERROR_HOOK, $onError);
}
public function onRun() : void{
@ -75,17 +70,6 @@ class ChunkRequestTask extends AsyncTask{
$this->setResult($this->compressor->deserialize()->compress($stream->getBuffer()));
}
public function onError() : void{
/**
* @var \Closure|null $hook
* @phpstan-var (\Closure() : void)|null $hook
*/
$hook = $this->fetchLocal(self::TLS_KEY_ERROR_HOOK);
if($hook !== null){
$hook();
}
}
public function onCompletion() : void{
/** @var CompressBatchPromise $promise */
$promise = $this->fetchLocal(self::TLS_KEY_PROMISE);

View File

@ -51,7 +51,7 @@ use const SORT_NUMERIC;
final class StandardEntityEventBroadcaster implements EntityEventBroadcaster{
public function __construct(
private StandardPacketBroadcaster $broadcaster,
private PacketBroadcaster $broadcaster,
private TypeConverter $typeConverter
){}

View File

@ -40,9 +40,21 @@ use function time;
class ProcessLoginTask extends AsyncTask{
private const TLS_KEY_ON_COMPLETION = "completion";
/**
* Old Mojang root auth key. This was used since the introduction of Xbox Live authentication in 0.15.0.
* This key is expected to be replaced by the key below in the future, but this has not yet happened as of
* 2023-07-01.
* Ideally we would place a time expiry on this key, but since Mojang have not given a hard date for the key change,
* and one bad guess has already caused a major outage, we can't do this.
* TODO: This needs to be removed as soon as the new key is deployed by Mojang's authentication servers.
*/
public const MOJANG_OLD_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
public const MOJANG_OLD_KEY_EXPIRY = 1688169600; //2023-07-01 00:00:00 UTC - there is no official date for the changeover to the new key, so this is a guess
/**
* New Mojang root auth key. Mojang notified third-party developers of this change prior to the release of 1.20.0.
* Expectations were that this would be used starting a "couple of weeks" after the release, but as of 2023-07-01,
* it has not yet been deployed.
*/
public const MOJANG_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp";
private const CLOCK_DRIFT_MAX = 60;
@ -162,7 +174,7 @@ class ProcessLoginTask extends AsyncTask{
throw new VerifyLoginException($e->getMessage(), null, 0, $e);
}
if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY || (time() < self::MOJANG_OLD_KEY_EXPIRY && $headers->x5u === self::MOJANG_OLD_ROOT_PUBLIC_KEY)){
if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY || $headers->x5u === self::MOJANG_OLD_ROOT_PUBLIC_KEY){
$this->authenticated = true; //we're signed into xbox live
}

View File

@ -118,14 +118,7 @@ class ChunkCache implements ChunkListener{
$chunkZ,
$chunk,
$this->caches[$chunkHash],
$this->compressor,
function() use ($chunkHash, $chunkX, $chunkZ) : void{
$this->world->getLogger()->error("Failed preparing chunk $chunkX $chunkZ, retrying");
if(isset($this->caches[$chunkHash])){
$this->restartPendingRequest($chunkX, $chunkZ);
}
}
$this->compressor
)
);

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\convert;
use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\data\bedrock\item\ItemDeserializer;
use pocketmine\data\bedrock\item\ItemSerializer;
use pocketmine\data\bedrock\item\ItemTypeDeserializeException;
@ -37,18 +38,19 @@ use pocketmine\utils\AssumptionFailedError;
* This class handles translation between network item ID+metadata to PocketMine-MP internal ID+metadata and vice versa.
*/
final class ItemTranslator{
public const NO_BLOCK_RUNTIME_ID = 0;
public const NO_BLOCK_RUNTIME_ID = 0; //this is technically a valid block runtime ID, but is used to represent "no block" (derp mojang)
public function __construct(
private ItemTypeDictionary $itemTypeDictionary,
private BlockStateDictionary $blockStateDictionary,
private ItemSerializer $itemSerializer,
private ItemDeserializer $itemDeserializer
private ItemDeserializer $itemDeserializer,
private BlockItemIdMap $blockItemIdMap
){}
/**
* @return int[]|null
* @phpstan-return array{int, int, int}|null
* @phpstan-return array{int, int, ?int}|null
*/
public function toNetworkIdQuiet(Item $item) : ?array{
try{
@ -60,7 +62,7 @@ final class ItemTranslator{
/**
* @return int[]
* @phpstan-return array{int, int, int}
* @phpstan-return array{int, int, ?int}
*
* @throws ItemTypeSerializeException
*/
@ -78,7 +80,7 @@ final class ItemTranslator{
throw new AssumptionFailedError("Unmapped blockstate returned by blockstate serializer: " . $blockStateData->toNbt());
}
}else{
$blockRuntimeId = self::NO_BLOCK_RUNTIME_ID; //this is technically a valid block runtime ID, but is used to represent "no block" (derp mojang)
$blockRuntimeId = null;
}
return [$numericId, $itemData->getMeta(), $blockRuntimeId];
@ -106,11 +108,13 @@ final class ItemTranslator{
}
$blockStateData = null;
if($networkBlockRuntimeId !== self::NO_BLOCK_RUNTIME_ID){
if($this->blockItemIdMap->lookupBlockId($stringId) !== null){
$blockStateData = $this->blockStateDictionary->generateDataFromStateId($networkBlockRuntimeId);
if($blockStateData === null){
throw new TypeConversionException("Blockstate runtimeID $networkBlockRuntimeId does not correspond to any known blockstate");
}
}elseif($networkBlockRuntimeId !== self::NO_BLOCK_RUNTIME_ID){
throw new TypeConversionException("Item $stringId is not a blockitem, but runtime ID $networkBlockRuntimeId was provided");
}
try{

View File

@ -82,7 +82,8 @@ class TypeConverter{
$this->itemTypeDictionary,
$this->blockTranslator->getBlockStateDictionary(),
GlobalItemDataHandlers::getSerializer(),
GlobalItemDataHandlers::getDeserializer()
GlobalItemDataHandlers::getDeserializer(),
$this->blockItemIdMap
);
$this->skinAdapter = new LegacySkinAdapter();
@ -147,7 +148,7 @@ class TypeConverter{
}elseif($ingredient instanceof ExactRecipeIngredient){
$item = $ingredient->getItem();
[$id, $meta, $blockRuntimeId] = $this->itemTranslator->toNetworkId($item);
if($blockRuntimeId !== ItemTranslator::NO_BLOCK_RUNTIME_ID){
if($blockRuntimeId !== null){
$meta = $this->blockTranslator->getBlockStateDictionary()->getMetaFromStateId($blockRuntimeId);
if($meta === null){
throw new AssumptionFailedError("Every block state should have an associated meta value");
@ -230,7 +231,7 @@ class TypeConverter{
$id,
$meta,
$itemStack->getCount(),
$blockRuntimeId,
$blockRuntimeId ?? ItemTranslator::NO_BLOCK_RUNTIME_ID,
$nbt,
[],
[],

View File

@ -1129,7 +1129,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
}
/**
* Sets the gamemode, and if needed, kicks the Player.
* Sets the provided gamemode.
*/
public function setGamemode(GameMode $gm) : bool{
if($this->gamemode->equals($gm)){

View File

@ -24,15 +24,17 @@ declare(strict_types=1);
namespace pocketmine\scheduler;
use pmmp\thread\Thread as NativeThread;
use pmmp\thread\ThreadSafeArray;
use pocketmine\snooze\SleeperHandler;
use pocketmine\thread\log\ThreadSafeLogger;
use pocketmine\thread\ThreadSafeClassLoader;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use function array_keys;
use function array_map;
use function assert;
use function count;
use function get_class;
use function spl_object_id;
use function time;
use const PHP_INT_MAX;
@ -130,6 +132,8 @@ class AsyncPool{
foreach($this->workerStartHooks as $hook){
$hook($workerId);
}
}else{
$this->checkCrashedWorker($workerId, null);
}
return $this->workers[$workerId];
@ -146,7 +150,6 @@ class AsyncPool{
throw new \InvalidArgumentException("Cannot submit the same AsyncTask instance more than once");
}
$task->progressUpdates = new ThreadSafeArray();
$task->setSubmitted();
$this->getWorker($worker)->submit($task);
@ -199,6 +202,28 @@ class AsyncPool{
return $worker;
}
private function checkCrashedWorker(int $workerId, ?AsyncTask $crashedTask) : void{
$entry = $this->workers[$workerId];
if($entry->worker->isTerminated()){
if($crashedTask === null){
foreach($entry->tasks as $task){
if($task->isTerminated()){
$crashedTask = $task;
break;
}elseif(!$task->isFinished()){
break;
}
}
}
if($crashedTask !== null){
$message = "Worker $workerId crashed while running task " . get_class($crashedTask) . "#" . spl_object_id($crashedTask);
}else{
$message = "Worker $workerId crashed for unknown reason";
}
throw new \RuntimeException($message);
}
}
/**
* Collects finished and/or crashed tasks from the workers, firing their on-completion hooks where appropriate.
*
@ -231,9 +256,9 @@ class AsyncPool{
if($task->isFinished()){ //make sure the task actually executed before trying to collect
$queue->dequeue();
if($task->isCrashed()){
$this->logger->critical("Could not execute asynchronous task " . (new \ReflectionClass($task))->getShortName() . ": Task crashed");
$task->onError();
if($task->isTerminated()){
$this->checkCrashedWorker($worker, $task);
throw new AssumptionFailedError("checkCrashedWorker() should have thrown an exception, making this unreachable");
}elseif(!$task->hasCancelledRun()){
/*
* It's possible for a task to submit a progress update and then finish before the progress
@ -244,11 +269,13 @@ class AsyncPool{
* lost. Thus, it's necessary to do one last check here to make sure all progress updates have
* been consumed before completing.
*/
$task->checkProgressUpdates();
$task->onCompletion();
$this->checkTaskProgressUpdates($task);
Timings::getAsyncTaskCompletionTimings($task)->time(function() use ($task) : void{
$task->onCompletion();
});
}
}else{
$task->checkProgressUpdates();
$this->checkTaskProgressUpdates($task);
$more = true;
break; //current task is still running, skip to next worker
}
@ -296,4 +323,10 @@ class AsyncPool{
}
$this->workers = [];
}
private function checkTaskProgressUpdates(AsyncTask $task) : void{
Timings::getAsyncTaskProgressUpdateTimings($task)->time(function() use ($task) : void{
$task->checkProgressUpdates();
});
}
}

View File

@ -61,34 +61,27 @@ use function spl_object_id;
*/
abstract class AsyncTask extends Runnable{
/**
* @var \ArrayObject|mixed[]|null object hash => mixed data
* @phpstan-var \ArrayObject<int, array<string, mixed>>|null
* @var mixed[][] object hash => mixed data
* @phpstan-var array<int, array<string, mixed>>
*
* Used to store objects which are only needed on one thread and should not be serialized.
* Used to store thread-local data to be used by onCompletion().
*/
private static ?\ArrayObject $threadLocalStorage = null;
private static array $threadLocalStorage = [];
/** @phpstan-var ThreadSafeArray<int, string> */
public ThreadSafeArray $progressUpdates;
/** @phpstan-var ThreadSafeArray<int, string>|null */
private ?ThreadSafeArray $progressUpdates = null;
private ThreadSafe|string|int|bool|null|float $result = null;
private bool $cancelRun = false;
private bool $submitted = false;
private bool $crashed = false;
private bool $finished = false;
public function run() : void{
$this->result = null;
if(!$this->cancelRun){
try{
$this->onRun();
}catch(\Throwable $e){
$this->crashed = true;
\GlobalLogger::get()->logException($e);
}
$this->onRun();
}
$this->finished = true;
@ -97,8 +90,11 @@ abstract class AsyncTask extends Runnable{
$worker->getNotifier()->wakeupSleeper();
}
/**
* @deprecated
*/
public function isCrashed() : bool{
return $this->crashed || $this->isTerminated();
return $this->isTerminated();
}
/**
@ -106,7 +102,7 @@ abstract class AsyncTask extends Runnable{
* because it is not true prior to task execution.
*/
public function isFinished() : bool{
return $this->finished || $this->isCrashed();
return $this->finished || $this->isTerminated();
}
public function hasResult() : bool{
@ -163,15 +159,22 @@ abstract class AsyncTask extends Runnable{
* @param mixed $progress A value that can be safely serialize()'ed.
*/
public function publishProgress(mixed $progress) : void{
$this->progressUpdates[] = igbinary_serialize($progress) ?? throw new \InvalidArgumentException("Progress must be serializable");
$progressUpdates = $this->progressUpdates;
if($progressUpdates === null){
$progressUpdates = $this->progressUpdates = new ThreadSafeArray();
}
$progressUpdates[] = igbinary_serialize($progress) ?? throw new \InvalidArgumentException("Progress must be serializable");
}
/**
* @internal Only call from AsyncPool.php on the main thread
*/
public function checkProgressUpdates() : void{
while(($progress = $this->progressUpdates->shift()) !== null){
$this->onProgressUpdate(igbinary_unserialize($progress));
$progressUpdates = $this->progressUpdates;
if($progressUpdates !== null){
while(($progress = $progressUpdates->shift()) !== null){
$this->onProgressUpdate(igbinary_unserialize($progress));
}
}
}
@ -188,8 +191,7 @@ abstract class AsyncTask extends Runnable{
}
/**
* Called from the main thread when the async task experiences an error during onRun(). Use this for things like
* promise rejection.
* @deprecated No longer used
*/
public function onError() : void{
@ -210,15 +212,6 @@ abstract class AsyncTask extends Runnable{
* from.
*/
protected function storeLocal(string $key, mixed $complexData) : void{
if(self::$threadLocalStorage === null){
/*
* It's necessary to use an object (not array) here because pthreads is stupid. Non-default array statics
* will be inherited when task classes are copied to the worker thread, which would cause unwanted
* inheritance of primitive thread-locals, which we really don't want for various reasons.
* It won't try to inherit objects though, so this is the easiest solution.
*/
self::$threadLocalStorage = new \ArrayObject();
}
self::$threadLocalStorage[spl_object_id($this)][$key] = $complexData;
}
@ -234,7 +227,7 @@ abstract class AsyncTask extends Runnable{
*/
protected function fetchLocal(string $key){
$id = spl_object_id($this);
if(self::$threadLocalStorage === null || !isset(self::$threadLocalStorage[$id][$key])){
if(!isset(self::$threadLocalStorage[$id][$key])){
throw new \InvalidArgumentException("No matching thread-local data found on this thread");
}
@ -243,12 +236,7 @@ abstract class AsyncTask extends Runnable{
final public function __destruct(){
$this->reallyDestruct();
if(self::$threadLocalStorage !== null && isset(self::$threadLocalStorage[$h = spl_object_id($this)])){
unset(self::$threadLocalStorage[$h]);
if(self::$threadLocalStorage->count() === 0){
self::$threadLocalStorage = null;
}
}
unset(self::$threadLocalStorage[spl_object_id($this)]);
}
/**

View File

@ -31,6 +31,7 @@ use pocketmine\thread\Worker;
use pocketmine\utils\AssumptionFailedError;
use function gc_enable;
use function ini_set;
use function set_exception_handler;
class AsyncWorker extends Worker{
/** @var mixed[] */
@ -67,6 +68,10 @@ class AsyncWorker extends Worker{
}
$this->saveToThreadStore(self::TLS_KEY_NOTIFIER, $this->sleeperEntry->createNotifier());
set_exception_handler(function(\Throwable $e){
$this->logger->logException($e);
});
}
public function getLogger() : ThreadSafeLogger{

View File

@ -29,6 +29,7 @@ use pocketmine\event\Event;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\ServerboundPacket;
use pocketmine\player\Player;
use pocketmine\scheduler\AsyncTask;
use pocketmine\scheduler\TaskHandler;
use function get_class;
use function str_starts_with;
@ -115,6 +116,17 @@ abstract class Timings{
/** @var TimingsHandler[][] */
private static array $eventHandlers = [];
private static TimingsHandler $asyncTaskProgressUpdateParent;
private static TimingsHandler $asyncTaskCompletionParent;
private static TimingsHandler $asyncTaskErrorParent;
/** @var TimingsHandler[] */
private static array $asyncTaskProgressUpdate = [];
/** @var TimingsHandler[] */
private static array $asyncTaskCompletion = [];
/** @var TimingsHandler[] */
private static array $asyncTaskError = [];
public static function init() : void{
if(self::$initialized){
return;
@ -168,7 +180,11 @@ abstract class Timings{
self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity", group: self::GROUP_BREAKDOWN);
self::$schedulerSync = new TimingsHandler("Scheduler - Sync Tasks", group: self::GROUP_BREAKDOWN);
self::$schedulerAsync = new TimingsHandler("Scheduler - Async Tasks", group: self::GROUP_BREAKDOWN);
self::$asyncTaskProgressUpdateParent = new TimingsHandler("Async Tasks - Progress Updates", self::$schedulerAsync, group: self::GROUP_BREAKDOWN);
self::$asyncTaskCompletionParent = new TimingsHandler("Async Tasks - Completion Handlers", self::$schedulerAsync, group: self::GROUP_BREAKDOWN);
self::$asyncTaskErrorParent = new TimingsHandler("Async Tasks - Error Handlers", self::$schedulerAsync, group: self::GROUP_BREAKDOWN);
self::$playerCommand = new TimingsHandler("Player Command", group: self::GROUP_BREAKDOWN);
self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache", group: self::GROUP_BREAKDOWN);
@ -299,4 +315,46 @@ abstract class Timings{
return self::$eventHandlers[$event][$handlerName];
}
public static function getAsyncTaskProgressUpdateTimings(AsyncTask $task, string $group = self::GROUP_BREAKDOWN) : TimingsHandler{
$taskClass = $task::class;
if(!isset(self::$asyncTaskProgressUpdate[$taskClass])){
self::init();
self::$asyncTaskProgressUpdate[$taskClass] = new TimingsHandler(
"AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Progress Updates",
self::$asyncTaskProgressUpdateParent,
$group
);
}
return self::$asyncTaskProgressUpdate[$taskClass];
}
public static function getAsyncTaskCompletionTimings(AsyncTask $task, string $group = self::GROUP_BREAKDOWN) : TimingsHandler{
$taskClass = $task::class;
if(!isset(self::$asyncTaskCompletion[$taskClass])){
self::init();
self::$asyncTaskCompletion[$taskClass] = new TimingsHandler(
"AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Completion Handler",
self::$asyncTaskCompletionParent,
$group
);
}
return self::$asyncTaskCompletion[$taskClass];
}
public static function getAsyncTaskErrorTimings(AsyncTask $task, string $group = self::GROUP_BREAKDOWN) : TimingsHandler{
$taskClass = $task::class;
if(!isset(self::$asyncTaskError[$taskClass])){
self::init();
self::$asyncTaskError[$taskClass] = new TimingsHandler(
"AsyncTask - " . self::shortenCoreClassName($taskClass, "pocketmine\\") . " - Error Handler",
self::$asyncTaskErrorParent,
$group
);
}
return self::$asyncTaskError[$taskClass];
}
}

View File

@ -2032,6 +2032,12 @@ class World implements ChunkManager{
if($clickVector === null){
$clickVector = new Vector3(0.0, 0.0, 0.0);
}else{
$clickVector = new Vector3(
min(1.0, max(0.0, $clickVector->x)),
min(1.0, max(0.0, $clickVector->y)),
min(1.0, max(0.0, $clickVector->z))
);
}
if(!$this->isInWorld($blockReplace->getPosition()->x, $blockReplace->getPosition()->y, $blockReplace->getPosition()->z)){

View File

@ -43,6 +43,7 @@ use pocketmine\world\WorldCreationOptions;
use Symfony\Component\Filesystem\Path;
use function array_map;
use function file_put_contents;
use function sprintf;
use function strlen;
use function substr;
use function time;
@ -154,12 +155,18 @@ class BedrockWorldData extends BaseNbtWorldData{
}
$version = $worldData->getInt(self::TAG_STORAGE_VERSION, Limits::INT32_MAX);
if($version === Limits::INT32_MAX){
throw new CorruptedWorldException(sprintf("Missing '%s' tag in level.dat", self::TAG_STORAGE_VERSION));
}
if($version > self::CURRENT_STORAGE_VERSION){
throw new UnsupportedWorldFormatException("LevelDB world format version $version is currently unsupported");
}
//StorageVersion is rarely updated - instead, the game relies on the NetworkVersion tag, which is synced with
//the network protocol version for that version.
$protocolVersion = $worldData->getInt(self::TAG_NETWORK_VERSION, Limits::INT32_MAX);
if($protocolVersion === Limits::INT32_MAX){
throw new CorruptedWorldException(sprintf("Missing '%s' tag in level.dat", self::TAG_NETWORK_VERSION));
}
if($protocolVersion > self::CURRENT_STORAGE_NETWORK_VERSION){
throw new UnsupportedWorldFormatException("LevelDB world protocol version $protocolVersion is currently unsupported");
}

File diff suppressed because one or more lines are too long

View File

@ -33,6 +33,7 @@ use pocketmine\crafting\json\ShapelessRecipeData;
use pocketmine\crafting\json\SmithingTransformRecipeData;
use pocketmine\crafting\json\SmithingTrimRecipeData;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\data\bedrock\item\BlockItemIdMap;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag;
@ -40,6 +41,7 @@ use pocketmine\nbt\tag\ListTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\convert\BlockStateDictionary;
use pocketmine\network\mcpe\convert\BlockTranslator;
use pocketmine\network\mcpe\convert\ItemTranslator;
use pocketmine\network\mcpe\handler\PacketHandler;
use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket;
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket;
@ -110,6 +112,7 @@ class ParserPacketHandler extends PacketHandler{
public ?ItemTypeDictionary $itemTypeDictionary = null;
private BlockTranslator $blockTranslator;
private BlockItemIdMap $blockItemIdMap;
public function __construct(private string $bedrockDataPath){
$this->blockTranslator = new BlockTranslator(
@ -119,6 +122,7 @@ class ParserPacketHandler extends PacketHandler{
),
GlobalBlockStateHandlers::getSerializer()
);
$this->blockItemIdMap = BlockItemIdMap::getInstance();
}
private static function blockStatePropertiesToString(BlockStateData $blockStateData) : string{
@ -136,7 +140,8 @@ class ParserPacketHandler extends PacketHandler{
if($this->itemTypeDictionary === null){
throw new PacketHandlingException("Can't process item yet; haven't received item type dictionary");
}
$data = new ItemStackData($this->itemTypeDictionary->fromIntId($itemStack->getId()));
$itemStringId = $this->itemTypeDictionary->fromIntId($itemStack->getId());
$data = new ItemStackData($itemStringId);
if($itemStack->getCount() !== 1){
$data->count = $itemStack->getCount();
@ -146,7 +151,7 @@ class ParserPacketHandler extends PacketHandler{
if($meta === 32767){
$meta = 0; //kick wildcard magic bullshit
}
if($itemStack->getBlockRuntimeId() !== 0){
if($this->blockItemIdMap->lookupBlockId($itemStringId) !== null){
if($meta !== 0){
throw new PacketHandlingException("Unexpected non-zero blockitem meta");
}
@ -159,6 +164,8 @@ class ParserPacketHandler extends PacketHandler{
if(count($stateProperties) > 0){
$data->block_states = self::blockStatePropertiesToString($blockState);
}
}elseif($itemStack->getBlockRuntimeId() !== ItemTranslator::NO_BLOCK_RUNTIME_ID){
throw new PacketHandlingException("Non-blockitems should have a zero block runtime ID");
}elseif($meta !== 0){
$data->meta = $meta;
}

View File

@ -26,20 +26,29 @@ namespace pocketmine\tools\generate_blockstate_upgrade_schema;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchema;
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaBlockRemap;
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenedName;
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaUtils;
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap;
use pocketmine\nbt\LittleEndianNbtSerializer;
use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\tag\Tag;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Filesystem;
use pocketmine\utils\Utils;
use function array_filter;
use function array_key_first;
use function array_merge;
use function array_keys;
use function array_map;
use function array_shift;
use function array_values;
use function assert;
use function count;
use function dirname;
use function explode;
use function file_put_contents;
use function fwrite;
use function implode;
use function json_encode;
use function ksort;
use function usort;
@ -56,9 +65,22 @@ class BlockStateMapping{
){}
}
/**
* @param Tag[] $properties
* @phpstan-param array<string, Tag> $properties
*/
function encodeOrderedProperties(array $properties) : string{
ksort($properties, SORT_STRING);
return implode("", array_map(fn(Tag $tag) => encodeProperty($tag), array_values($properties)));
}
function encodeProperty(Tag $tag) : string{
return (new LittleEndianNbtSerializer())->write(new TreeRoot($tag));
}
/**
* @return BlockStateMapping[][]
* @phpstan-return array<string, list<BlockStateMapping>>
* @phpstan-return array<string, array<string, BlockStateMapping>>
*/
function loadUpgradeTable(string $file, bool $reverse) : array{
$contents = Filesystem::fileGetContents($file);
@ -72,7 +94,7 @@ function loadUpgradeTable(string $file, bool $reverse) : array{
$old = BlockStateData::fromNbt($reverse ? $newTag : $oldTag);
$new = BlockStateData::fromNbt($reverse ? $oldTag : $newTag);
$result[$old->getName()][] = new BlockStateMapping(
$result[$old->getName()][encodeOrderedProperties($old->getStates())] = new BlockStateMapping(
$old,
$new
);
@ -82,111 +104,176 @@ function loadUpgradeTable(string $file, bool $reverse) : array{
}
/**
* @param true[] $removedPropertiesCache
* @param Tag[][] $remappedPropertyValuesCache
* @phpstan-param array<string, true> $removedPropertiesCache
* @phpstan-param array<string, array<string, Tag>> $remappedPropertyValuesCache
* @param BlockStateData[] $states
* @phpstan-param array<string, BlockStateData> $states
*
* @return Tag[][]
* @phpstan-return array<string, array<string, Tag>>
*/
function processState(BlockStateData $old, BlockStateData $new, BlockStateUpgradeSchema $result, array &$removedPropertiesCache, array &$remappedPropertyValuesCache) : void{
function buildStateGroupSchema(array $states) : ?array{
$first = $states[array_key_first($states)];
//new and old IDs are the same; compare states
$oldName = $old->getName();
$properties = [];
foreach(Utils::stringifyKeys($first->getStates()) as $propertyName => $propertyValue){
$properties[$propertyName][encodeProperty($propertyValue)] = $propertyValue;
}
foreach($states as $state){
if(count($state->getStates()) !== count($properties)){
return null;
}
foreach(Utils::stringifyKeys($state->getStates()) as $propertyName => $propertyValue){
if(!isset($properties[$propertyName])){
return null;
}
$properties[$propertyName][encodeProperty($propertyValue)] = $propertyValue;
}
}
$oldStates = $old->getStates();
$newStates = $new->getStates();
return $properties;
}
$propertyRemoved = [];
$propertyAdded = [];
foreach(Utils::stringifyKeys($oldStates) as $propertyName => $oldProperty){
$newProperty = $new->getState($propertyName);
if($newProperty === null){
$propertyRemoved[$propertyName] = $oldProperty;
}elseif(!$newProperty->equals($oldProperty)){
if(!isset($remappedPropertyValuesCache[$propertyName][$oldProperty->getValue()])){
$result->remappedPropertyValues[$oldName][$propertyName][] = new BlockStateUpgradeSchemaValueRemap(
$oldProperty,
$newProperty
);
$remappedPropertyValuesCache[$propertyName][$oldProperty->getValue()] = $newProperty;
/**
* @param BlockStateMapping[] $upgradeTable
* @phpstan-param array<string, BlockStateMapping> $upgradeTable
*/
function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgradeSchema $result) : bool{
$newProperties = buildStateGroupSchema(array_map(fn(BlockStateMapping $m) => $m->new, $upgradeTable));
if($newProperties === null){
\GlobalLogger::get()->warning("New states for $oldName don't all have the same set of properties - processing as remaps instead");
return false;
}
$oldProperties = buildStateGroupSchema(array_map(fn(BlockStateMapping $m) => $m->old, $upgradeTable));
if($oldProperties === null){
//TODO: not sure if this is actually required - we may be able to apply some transformations even if the states are not consistent
//however, this should never normally occur anyway
\GlobalLogger::get()->warning("Old states for $oldName don't all have the same set of properties - processing as remaps instead");
return false;
}
$remappedPropertyValues = [];
$addedProperties = [];
$removedProperties = [];
$renamedProperties = [];
foreach(Utils::stringifyKeys($newProperties) as $newPropertyName => $newPropertyValues){
if(count($newPropertyValues) === 1){
$newPropertyValue = $newPropertyValues[array_key_first($newPropertyValues)];
if(isset($oldProperties[$newPropertyName])){
//all the old values for this property were mapped to the same new value
//it would be more space-efficient to represent this as a remove+add, but we can't guarantee that the
//removal of the old value will be done before the addition of the new value
foreach($oldProperties[$newPropertyName] as $oldPropertyValue){
$remappedPropertyValues[$newPropertyName][encodeProperty($oldPropertyValue)] = $newPropertyValue;
}
}else{
//this property has no relation to any property value in any of the old states - it's a new property
$addedProperties[$newPropertyName] = $newPropertyValue;
}
}
}
foreach(Utils::stringifyKeys($newStates) as $propertyName => $value){
if($old->getState($propertyName) === null){
$propertyAdded[$propertyName] = $value;
}
}
if(count($propertyAdded) === 0 && count($propertyRemoved) === 0){
return;
}
if(count($propertyAdded) === 1 && count($propertyRemoved) === 1){
$propertyOldName = array_key_first($propertyRemoved);
$propertyNewName = array_key_first($propertyAdded);
$propertyOldValue = $propertyRemoved[$propertyOldName];
$propertyNewValue = $propertyAdded[$propertyNewName];
$existingPropertyValueMap = $remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()] ?? null;
if($propertyOldName !== $propertyNewName){
if(!$propertyOldValue->equals($propertyNewValue) && $existingPropertyValueMap === null){
\GlobalLogger::get()->warning("warning: guessing that $oldName has $propertyOldName renamed to $propertyNewName with a value map of $propertyOldValue mapped to $propertyNewValue");;
}
//this is a guess; it might not be reliable if the value changed as well
//this will probably never be an issue, but it might rear its ugly head in the future
$result->renamedProperties[$oldName][$propertyOldName] = $propertyNewName;
}
if(!$propertyOldValue->equals($propertyNewValue)){
$mapped = true;
if($existingPropertyValueMap !== null && !$existingPropertyValueMap->equals($propertyNewValue)){
if($existingPropertyValueMap->equals($propertyOldValue)){
\GlobalLogger::get()->warning("warning: guessing that the value $propertyOldValue of $propertyNewValue did not change");;
$mapped = false;
}else{
\GlobalLogger::get()->warning("warning: mismatch of new value for $propertyNewName for $oldName: $propertyOldValue seen mapped to $propertyNewValue and $existingPropertyValueMap");;
foreach(Utils::stringifyKeys($oldProperties) as $oldPropertyName => $oldPropertyValues){
$mappingsContainingOldValue = [];
foreach($upgradeTable as $mapping){
$mappingOldValue = $mapping->old->getState($oldPropertyName) ?? throw new AssumptionFailedError("This should never happen");
foreach($oldPropertyValues as $oldPropertyValue){
if($mappingOldValue->equals($oldPropertyValue)){
$mappingsContainingOldValue[encodeProperty($oldPropertyValue)][] = $mapping;
break;
}
}
if($mapped && !isset($remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()])){
//value remap
$result->remappedPropertyValues[$oldName][$propertyOldName][] = new BlockStateUpgradeSchemaValueRemap(
$propertyRemoved[$propertyOldName],
$propertyAdded[$propertyNewName]
);
$remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()] = $propertyNewValue;
}
}elseif($existingPropertyValueMap !== null){
\GlobalLogger::get()->warning("warning: multiple values found for value $propertyOldValue of $propertyNewName on block $oldName, guessing it did not change");;
$remappedPropertyValuesCache[$propertyOldName][$propertyOldValue->getValue()] = $propertyNewValue;
}
}else{
if(count($propertyAdded) !== 0 && count($propertyRemoved) === 0){
foreach(Utils::stringifyKeys($propertyAdded) as $propertyAddedName => $propertyAddedValue){
$existingDefault = $result->addedProperties[$oldName][$propertyAddedName] ?? null;
if($existingDefault !== null && !$existingDefault->equals($propertyAddedValue)){
throw new \UnexpectedValueException("Ambiguous default value for added property $propertyAddedName on block $oldName");
}
$result->addedProperties[$oldName][$propertyAddedName] = $propertyAddedValue;
$candidateNewPropertyNames = [];
//foreach mappings by unique value, compute the diff across all the states in the list
foreach(Utils::stringifyKeys($mappingsContainingOldValue) as $rawOldValue => $mappingList){
$first = array_shift($mappingList);
foreach(Utils::stringifyKeys($first->new->getStates()) as $newPropertyName => $newPropertyValue){
if(isset($addedProperties[$newPropertyName])){
//this property was already determined to be unrelated to any old property
continue;
}
foreach($mappingList as $pair){
if(!($pair->new->getState($newPropertyName)?->equals($newPropertyValue) ?? false)){
//if the new property is different with an unchanged old value,
//the property may be influenced by multiple old properties, or be unrelated entirely
continue 2;
}
}
$candidateNewPropertyNames[$newPropertyName][$rawOldValue] = $newPropertyValue;
}
}elseif(count($propertyRemoved) !== 0 && count($propertyAdded) === 0){
foreach(Utils::stringifyKeys($propertyRemoved) as $propertyRemovedName => $propertyRemovedValue){
if(!isset($removedPropertiesCache[$propertyRemovedName])){
//to avoid having useless keys in the output
$result->removedProperties[$oldName][] = $propertyRemovedName;
$removedPropertiesCache[$propertyRemovedName] = $propertyRemovedName;
}
if(count($candidateNewPropertyNames) === 0){
$removedProperties[$oldPropertyName] = $oldPropertyName;
}elseif(count($candidateNewPropertyNames) === 1){
$newPropertyName = array_key_first($candidateNewPropertyNames);
$newPropertyValues = $candidateNewPropertyNames[$newPropertyName];
if($oldPropertyName !== $newPropertyName){
$renamedProperties[$oldPropertyName] = $newPropertyName;
}
foreach(Utils::stringifyKeys($newPropertyValues) as $rawOldValue => $newPropertyValue){
if(!$newPropertyValue->equals($oldPropertyValues[$rawOldValue])){
$remappedPropertyValues[$oldPropertyName][$rawOldValue] = $newPropertyValue;
}
}
}else{
$result->remappedStates[$oldName][] = new BlockStateUpgradeSchemaBlockRemap(
$oldStates,
$new->getName(),
$newStates,
[]
);
\GlobalLogger::get()->warning("warning: multiple properties added and removed for $oldName; added full state remap");;
$split = true;
if(isset($candidateNewPropertyNames[$oldPropertyName])){
//In 1.10, direction wasn't changed at all, but not all state permutations were present in the palette,
//making it appear that door_hinge_bit was correlated with direction.
//If a new property is present with the same name and values as an old property, we can assume that
//the property was unchanged, and that any extra matches properties are probably unrelated.
$changedValues = false;
foreach(Utils::stringifyKeys($candidateNewPropertyNames[$oldPropertyName]) as $rawOldValue => $newPropertyValue){
if(!$newPropertyValue->equals($oldPropertyValues[$rawOldValue])){
//if any of the new values are different, we may be dealing with a property being split into
//multiple new properties - hand this off to the remap handler
$changedValues = true;
break;
}
}
if(!$changedValues){
$split = false;
}
}
if($split){
\GlobalLogger::get()->warning(
"Multiple new properties (" . (implode(", ", array_keys($candidateNewPropertyNames))) . ") are correlated with $oldName property $oldPropertyName, processing as remaps instead"
);
return false;
}else{
//is it safe to ignore the rest?
}
}
}
//finally, write the results to the schema
if(count($remappedPropertyValues) !== 0){
foreach(Utils::stringifyKeys($remappedPropertyValues) as $oldPropertyName => $propertyValues){
foreach(Utils::stringifyKeys($propertyValues) as $rawOldValue => $newPropertyValue){
$oldPropertyValue = $oldProperties[$oldPropertyName][$rawOldValue];
$result->remappedPropertyValues[$oldName][$oldPropertyName][] = new BlockStateUpgradeSchemaValueRemap(
$oldPropertyValue,
$newPropertyValue
);
}
}
}
if(count($addedProperties) !== 0){
$result->addedProperties[$oldName] = $addedProperties;
}
if(count($removedProperties) !== 0){
$result->removedProperties[$oldName] = array_values($removedProperties);
}
if(count($renamedProperties) !== 0){
$result->renamedProperties[$oldName] = $renamedProperties;
}
return true;
}
/**
@ -194,17 +281,18 @@ function processState(BlockStateData $old, BlockStateData $new, BlockStateUpgrad
* This significantly reduces the output size during flattening when the flattened block has many permutations
* (e.g. walls).
*
* @param BlockStateUpgradeSchemaBlockRemap[] $stateRemaps
* @param BlockStateMapping[] $upgradeTable
* @param BlockStateMapping[] $upgradeTable
* @phpstan-param array<string, BlockStateMapping> $upgradeTable
*
* @return BlockStateUpgradeSchemaBlockRemap[]
* @phpstan-return list<BlockStateUpgradeSchemaBlockRemap>
*/
function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array{
function processRemappedStates(array $upgradeTable) : array{
$unchangedStatesByNewName = [];
foreach($upgradeTable as $pair){
if(count($pair->old->getStates()) === 0 || count($pair->new->getStates()) === 0){
//all states have changed in some way - compression not possible
//all states have changed in some way - no states are copied over
$unchangedStatesByNewName[$pair->new->getName()] = [];
continue;
}
@ -240,78 +328,115 @@ function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array
$unchangedStatesByNewName[$newName] = $unchangedStates;
}
$compressedRemaps = [];
$flattenedProperties = [];
$notFlattenedProperties = [];
$notFlattenedPropertyValues = [];
foreach($upgradeTable as $pair){
foreach(Utils::stringifyKeys($pair->old->getStates()) as $propertyName => $propertyValue){
if(isset($notFlattenedProperties[$propertyName])){
continue;
}
if(!$propertyValue instanceof StringTag){
$notFlattenedProperties[$propertyName] = true;
continue;
}
$rawValue = $propertyValue->getValue();
if($rawValue === ""){
$notFlattenedProperties[$propertyName] = true;
continue;
}
$parts = explode($rawValue, $pair->new->getName(), 2);
if(count($parts) !== 2){
//the new name does not contain the property value, but it may still be able to be flattened in other cases
$notFlattenedPropertyValues[$propertyName][$rawValue] = $rawValue;
continue;
}
[$prefix, $suffix] = $parts;
foreach($stateRemaps as $remap){
$oldState = $remap->oldState;
$newState = $remap->newState;
if($oldState === null || $newState === null){
//no unchanged states - no compression possible
assert(!isset($unchangedStatesByNewName[$remap->newName]));
$compressedRemaps[$remap->newName][] = $remap;
continue;
$filter = $pair->old->getStates();
foreach($unchangedStatesByNewName[$pair->new->getName()] as $unchangedPropertyName){
unset($filter[$unchangedPropertyName]);
}
unset($filter[$propertyName]);
$rawFilter = encodeOrderedProperties($filter);
$flattenRule = new BlockStateUpgradeSchemaFlattenedName(
prefix: $prefix,
flattenedProperty: $propertyName,
suffix: $suffix
);
if(!isset($flattenedProperties[$propertyName][$rawFilter])){
$flattenedProperties[$propertyName][$rawFilter] = $flattenRule;
}elseif(!$flattenRule->equals($flattenedProperties[$propertyName][$rawFilter])){
$notFlattenedProperties[$propertyName] = true;
}
}
}
foreach(Utils::stringifyKeys($notFlattenedProperties) as $propertyName => $_){
unset($flattenedProperties[$propertyName]);
}
ksort($flattenedProperties, SORT_STRING);
$flattenProperty = array_key_first($flattenedProperties);
$list = [];
foreach($upgradeTable as $pair){
$oldState = $pair->old->getStates();
$newState = $pair->new->getStates();
$cleanedOldState = $oldState;
$cleanedNewState = $newState;
$newName = $pair->new->getName();
foreach($unchangedStatesByNewName[$remap->newName] as $propertyName){
foreach($unchangedStatesByNewName[$newName] as $propertyName){
unset($cleanedOldState[$propertyName]);
unset($cleanedNewState[$propertyName]);
}
ksort($cleanedOldState);
ksort($cleanedNewState);
$duplicate = false;
$compressedRemaps[$remap->newName] ??= [];
foreach($compressedRemaps[$remap->newName] as $k => $compressedRemap){
assert($compressedRemap->oldState !== null && $compressedRemap->newState !== null);
if(
count($compressedRemap->oldState) !== count($cleanedOldState) ||
count($compressedRemap->newState) !== count($cleanedNewState)
){
continue;
$flattened = false;
if($flattenProperty !== null){
$flattenedValue = $cleanedOldState[$flattenProperty] ?? null;
if(!$flattenedValue instanceof StringTag){
throw new AssumptionFailedError("This should always be a TAG_String");
}
foreach(Utils::stringifyKeys($cleanedOldState) as $propertyName => $propertyValue){
if(!isset($compressedRemap->oldState[$propertyName]) || !$compressedRemap->oldState[$propertyName]->equals($propertyValue)){
//different filter value
continue 2;
}
if(!isset($notFlattenedPropertyValues[$flattenProperty][$flattenedValue->getValue()])){
unset($cleanedOldState[$flattenProperty]);
$flattened = true;
}
foreach(Utils::stringifyKeys($cleanedNewState) as $propertyName => $propertyValue){
if(!isset($compressedRemap->newState[$propertyName]) || !$compressedRemap->newState[$propertyName]->equals($propertyValue)){
//different replacement value
continue 2;
}
}
$duplicate = true;
break;
}
if(!$duplicate){
$compressedRemaps[$remap->newName][] = new BlockStateUpgradeSchemaBlockRemap(
$cleanedOldState,
$remap->newName,
$cleanedNewState,
$unchangedStatesByNewName[$remap->newName]
);
$rawOldState = encodeOrderedProperties($cleanedOldState);
$newNameRule = $flattenProperty !== null && $flattened ?
$flattenedProperties[$flattenProperty][$rawOldState] ?? throw new AssumptionFailedError("This should always be set") :
$newName;
$remap = new BlockStateUpgradeSchemaBlockRemap(
$cleanedOldState,
$newNameRule,
$cleanedNewState,
$unchangedStatesByNewName[$pair->new->getName()]
);
$existing = $list[$rawOldState] ?? null;
if($existing === null || $existing->equals($remap)){
$list[$rawOldState] = $remap;
}else{
//match criteria is borked
throw new AssumptionFailedError("Match criteria resulted in two ambiguous remaps");
}
}
$list = array_merge(...array_values($compressedRemaps));
//more specific filters must come before less specific ones, in case of a remap on a certain value which is
//otherwise unchanged
usort($list, function(BlockStateUpgradeSchemaBlockRemap $a, BlockStateUpgradeSchemaBlockRemap $b) : int{
return count($b->oldState) <=> count($a->oldState);
});
return $list;
return array_values($list);
}
/**
* @param BlockStateMapping[][] $upgradeTable
* @phpstan-param array<string, list<BlockStateMapping>> $upgradeTable
* @phpstan-param array<string, array<string, BlockStateMapping>> $upgradeTable
*/
function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgradeSchema{
$foundVersion = -1;
@ -343,8 +468,6 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad
foreach(Utils::stringifyKeys($upgradeTable) as $oldName => $blockStateMappings){
$newNameFound = [];
$removedPropertiesCache = [];
$remappedPropertyValuesCache = [];
foreach($blockStateMappings as $mapping){
$newName = $mapping->new->getName();
$newNameFound[$newName] = true;
@ -354,35 +477,23 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad
if($newName !== $oldName){
$result->renamedIds[$oldName] = array_key_first($newNameFound);
}
foreach($blockStateMappings as $mapping){
processState($mapping->old, $mapping->new, $result, $removedPropertiesCache, $remappedPropertyValuesCache);
if(!processStateGroup($oldName, $blockStateMappings, $result)){
throw new \RuntimeException("States with the same ID should be fully consistent");
}
}else{
if(isset($newNameFound[$oldName])){
//some of the states stayed under the same ID - we can process these as normal states
foreach($blockStateMappings as $k => $mapping){
if($mapping->new->getName() === $oldName){
processState($mapping->old, $mapping->new, $result, $removedPropertiesCache, $remappedPropertyValuesCache);
$stateGroup = array_filter($blockStateMappings, fn(BlockStateMapping $m) => $m->new->getName() === $oldName);
if(processStateGroup($oldName, $stateGroup, $result)){
foreach(Utils::stringifyKeys($stateGroup) as $k => $mapping){
unset($blockStateMappings[$k]);
}
}
}
//block mapped to multiple different new IDs; we can't guess these, so we just do a plain old remap
foreach($blockStateMappings as $mapping){
if(!$mapping->old->equals($mapping->new)){
$result->remappedStates[$mapping->old->getName()][] = new BlockStateUpgradeSchemaBlockRemap(
$mapping->old->getStates(),
$mapping->new->getName(),
$mapping->new->getStates(),
[]
);
}
}
$result->remappedStates[$oldName] = processRemappedStates($blockStateMappings);
}
}
foreach(Utils::stringifyKeys($result->remappedStates) as $oldName => $remap){
$result->remappedStates[$oldName] = compressRemappedStates($upgradeTable[$oldName], $remap);
}
return $result;
}