From eb9f804781a8b5d319691877c2fca3b55131a2fa Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 19 Jun 2023 12:22:50 +0100 Subject: [PATCH 01/13] =?UTF-8?q?=C3=82BedrockWorldData:=20throw=20less=20?= =?UTF-8?q?confusing=20errors=20on=20missing=20version=20tags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/world/format/io/data/BedrockWorldData.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/world/format/io/data/BedrockWorldData.php b/src/world/format/io/data/BedrockWorldData.php index 3a24e9a11c..317ebd6801 100644 --- a/src/world/format/io/data/BedrockWorldData.php +++ b/src/world/format/io/data/BedrockWorldData.php @@ -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"); } From be8cca1d55d3e50972e6a2dee86214dbe0ebf930 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:25:26 +0100 Subject: [PATCH 02/13] Bump docker/build-push-action from 4.1.0 to 4.1.1 (#5834) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-docker-image.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 02342bbfd2..c6a5a8ed2a 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -53,7 +53,7 @@ jobs: run: echo NAME=$(echo "${GITHUB_REPOSITORY,,}") >> $GITHUB_OUTPUT - name: Build image for tag - uses: docker/build-push-action@v4.1.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.1.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.1.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.1.0 + uses: docker/build-push-action@v4.1.1 with: push: true context: ./pocketmine-mp From 391732f00c589e9df243de97e1719dda1ae3fdc6 Mon Sep 17 00:00:00 2001 From: Artem Vasyagin <58974140+Lunarelly@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:29:48 +0300 Subject: [PATCH 03/13] Fix `Player->setGamemode()` doc comment (#5848) this has been outdated likely since the 1.3 alpha days. --- src/player/Player.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/player/Player.php b/src/player/Player.php index 3083538be6..11dabf7db6 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -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)){ From 9d0d60afd1312e4bf83b173d3964165590b41723 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 21 Jun 2023 15:36:48 +0100 Subject: [PATCH 04/13] BlockPlaceEvent: ensure that getPosition() is always correct since BlockTransaction was designed to be World-agnostic, it can't position() any blocks, since Position requires a World. This workaround is the best we can do for now; however, it would probably be wise to deprecate getTransaction() in favour of a dedicated getBlocks() method which takes care of this, as BlockPlaceEvent is currently quite obnoxious to use. --- src/event/block/BlockPlaceEvent.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/event/block/BlockPlaceEvent.php b/src/event/block/BlockPlaceEvent.php index b92569fc16..3572cb5e09 100644 --- a/src/event/block/BlockPlaceEvent.php +++ b/src/event/block/BlockPlaceEvent.php @@ -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. From 6f82942c640c851ab6b98586adee1f311cbc6a99 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 21 Jun 2023 16:57:39 +0100 Subject: [PATCH 05/13] Block: document onInteract() clickVector --- src/block/Block.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/block/Block.php b/src/block/Block.php index f2268aed93..b4203e6b6b 100644 --- a/src/block/Block.php +++ b/src/block/Block.php @@ -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; From 8dedbb747108c45b7534b8ac6f05e8464ee8eb22 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 21 Jun 2023 16:59:14 +0100 Subject: [PATCH 06/13] World: clamp clickVector components from 0-1 this ensures that #5827 won't randomly start crashing if clients give bad values. --- src/world/World.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/world/World.php b/src/world/World.php index b2b6dfac2a..deb03ef6dd 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -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)){ From 881451c40cf3ea0acc6b76a4f1386c5da7d27b52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:00:59 +0100 Subject: [PATCH 07/13] Bump build/php from `8cb2a2b` to `2a21c57` (#5856) Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `8cb2a2b` to `2a21c57`. - [Release notes](https://github.com/pmmp/php-build-scripts/releases) - [Commits](https://github.com/pmmp/php-build-scripts/compare/8cb2a2b2181fd42192665985696ea157aa4f731e...2a21c579007a8fd7244f9faad498cd1c8c33004c) --- updated-dependencies: - dependency-name: build/php dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index 8cb2a2b218..2a21c57900 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 8cb2a2b2181fd42192665985696ea157aa4f731e +Subproject commit 2a21c579007a8fd7244f9faad498cd1c8c33004c From c06763c59bbdc3249a46ae10dddc544187f4054d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 23 Jun 2023 12:55:47 +0100 Subject: [PATCH 08/13] Fixed crash in CakeWithCandle when block-picking --- src/block/CakeWithCandle.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/block/CakeWithCandle.php b/src/block/CakeWithCandle.php index 187442cd98..4479faee10 100644 --- a/src/block/CakeWithCandle.php +++ b/src/block/CakeWithCandle.php @@ -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{ From b8788c55c58effd04f5d8774ae04dd1656813abb Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 24 Jun 2023 16:14:28 +0100 Subject: [PATCH 09/13] tools/generate-blockstate-upgrade-schema: improve property remapping checks this is now able to determine which properties were renamed and/or changed when multiple renames occurred in a single version. This also fixes unrelated properties being considered mapped to each other when there was only one property in the old and new state (e.g. mapped_type and deprecated for hay_bale in 1.10). Now, these are properly considered as unrelated. --- tools/generate-blockstate-upgrade-schema.php | 281 ++++++++++++------- 1 file changed, 184 insertions(+), 97 deletions(-) diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php index 847486a668..691f608fad 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/generate-blockstate-upgrade-schema.php @@ -28,18 +28,26 @@ use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchema; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaBlockRemap; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaUtils; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap; +use pocketmine\nbt\LittleEndianNbtSerializer; 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_keys; +use function array_map; use function array_merge; +use function array_shift; use function array_values; use function assert; use function count; use function dirname; use function file_put_contents; use function fwrite; +use function implode; use function json_encode; use function ksort; use function usort; @@ -56,9 +64,22 @@ class BlockStateMapping{ ){} } +/** + * @param Tag[] $properties + * @phpstan-param array $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> + * @phpstan-return array> */ function loadUpgradeTable(string $file, bool $reverse) : array{ $contents = Filesystem::fileGetContents($file); @@ -72,7 +93,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 +103,176 @@ function loadUpgradeTable(string $file, bool $reverse) : array{ } /** - * @param true[] $removedPropertiesCache - * @param Tag[][] $remappedPropertyValuesCache - * @phpstan-param array $removedPropertiesCache - * @phpstan-param array> $remappedPropertyValuesCache + * @param BlockStateData[] $states + * @phpstan-param array $states + * + * @return Tag[][] + * @phpstan-return array> */ -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 $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; } /** @@ -196,8 +282,11 @@ function processState(BlockStateData $old, BlockStateData $new, BlockStateUpgrad * * @param BlockStateUpgradeSchemaBlockRemap[] $stateRemaps * @param BlockStateMapping[] $upgradeTable + * @phpstan-param list $stateRemaps + * @phpstan-param array $upgradeTable * * @return BlockStateUpgradeSchemaBlockRemap[] + * @phpstan-return list */ function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array{ $unchangedStatesByNewName = []; @@ -311,7 +400,7 @@ function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array /** * @param BlockStateMapping[][] $upgradeTable - * @phpstan-param array> $upgradeTable + * @phpstan-param array> $upgradeTable */ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgradeSchema{ $foundVersion = -1; @@ -343,8 +432,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,15 +441,15 @@ 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]); } } From ad67fb729138c7b1e27e9bb0be604463d136b5de Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Sat, 24 Jun 2023 16:22:29 +0100 Subject: [PATCH 10/13] BlockStateUpgradeSchemaModelBlockRemap: added missing @required tag --- .../upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php index 49b2e0f285..d37a72fb8a 100644 --- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php +++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php @@ -40,6 +40,7 @@ final class BlockStateUpgradeSchemaModelBlockRemap{ /** * @var BlockStateUpgradeSchemaModelTag[]|null * @phpstan-var array|null + * @required */ public ?array $newState; From 470a3e1a3acfeb3f524e47082160ab6461b2474f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 26 Jun 2023 12:40:17 +0100 Subject: [PATCH 11/13] tools/generate-blockstate-upgrade-schema: reduce dependencies for generating blockstate mappings --- tools/generate-blockstate-upgrade-schema.php | 53 ++++++++------------ 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php index 691f608fad..70591fa282 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/generate-blockstate-upgrade-schema.php @@ -280,15 +280,13 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra * This significantly reduces the output size during flattening when the flattened block has many permutations * (e.g. walls). * - * @param BlockStateUpgradeSchemaBlockRemap[] $stateRemaps - * @param BlockStateMapping[] $upgradeTable - * @phpstan-param list $stateRemaps + * @param BlockStateMapping[] $upgradeTable * @phpstan-param array $upgradeTable * * @return BlockStateUpgradeSchemaBlockRemap[] * @phpstan-return list */ -function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array{ +function processRemappedStates(array $upgradeTable) : array{ $unchangedStatesByNewName = []; foreach($upgradeTable as $pair){ @@ -331,21 +329,26 @@ function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array $compressedRemaps = []; - foreach($stateRemaps as $remap){ - $oldState = $remap->oldState; - $newState = $remap->newState; + foreach($upgradeTable as $remap){ + $oldState = $remap->old->getStates(); + $newState = $remap->new->getStates(); - if($oldState === null || $newState === null){ - //no unchanged states - no compression possible - assert(!isset($unchangedStatesByNewName[$remap->newName])); - $compressedRemaps[$remap->newName][] = $remap; + if(count($oldState) === 0 || count($newState) === 0){ + //all states have changed in some way - compression not possible + assert(!isset($unchangedStatesByNewName[$remap->new->getName()])); + $compressedRemaps[$remap->new->getName()][] = new BlockStateUpgradeSchemaBlockRemap( + $oldState, + $remap->new->getName(), + $newState, + [] + ); continue; } $cleanedOldState = $oldState; $cleanedNewState = $newState; - foreach($unchangedStatesByNewName[$remap->newName] as $propertyName){ + foreach($unchangedStatesByNewName[$remap->new->getName()] as $propertyName){ unset($cleanedOldState[$propertyName]); unset($cleanedNewState[$propertyName]); } @@ -353,10 +356,8 @@ function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array ksort($cleanedNewState); $duplicate = false; - $compressedRemaps[$remap->newName] ??= []; - foreach($compressedRemaps[$remap->newName] as $k => $compressedRemap){ - assert($compressedRemap->oldState !== null && $compressedRemap->newState !== null); - + $compressedRemaps[$remap->new->getName()] ??= []; + foreach($compressedRemaps[$remap->new->getName()] as $k => $compressedRemap){ if( count($compressedRemap->oldState) !== count($cleanedOldState) || count($compressedRemap->newState) !== count($cleanedNewState) @@ -379,11 +380,11 @@ function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array break; } if(!$duplicate){ - $compressedRemaps[$remap->newName][] = new BlockStateUpgradeSchemaBlockRemap( + $compressedRemaps[$remap->new->getName()][] = new BlockStateUpgradeSchemaBlockRemap( $cleanedOldState, - $remap->newName, + $remap->new->getName(), $cleanedNewState, - $unchangedStatesByNewName[$remap->newName] + $unchangedStatesByNewName[$remap->new->getName()] ); } } @@ -455,21 +456,9 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad } } //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; } From 74d219dcb6a57788d453e3c865479148ea333902 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 26 Jun 2023 16:07:23 +0100 Subject: [PATCH 12/13] Revert "tools/generate-blockstate-upgrade-schema: reduce dependencies for generating blockstate mappings" This reverts commit 470a3e1a3acfeb3f524e47082160ab6461b2474f. This changes behaviour, so it needs to target minor-next. --- tools/generate-blockstate-upgrade-schema.php | 53 ++++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php index 70591fa282..691f608fad 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/generate-blockstate-upgrade-schema.php @@ -280,13 +280,15 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra * This significantly reduces the output size during flattening when the flattened block has many permutations * (e.g. walls). * - * @param BlockStateMapping[] $upgradeTable + * @param BlockStateUpgradeSchemaBlockRemap[] $stateRemaps + * @param BlockStateMapping[] $upgradeTable + * @phpstan-param list $stateRemaps * @phpstan-param array $upgradeTable * * @return BlockStateUpgradeSchemaBlockRemap[] * @phpstan-return list */ -function processRemappedStates(array $upgradeTable) : array{ +function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array{ $unchangedStatesByNewName = []; foreach($upgradeTable as $pair){ @@ -329,26 +331,21 @@ function processRemappedStates(array $upgradeTable) : array{ $compressedRemaps = []; - foreach($upgradeTable as $remap){ - $oldState = $remap->old->getStates(); - $newState = $remap->new->getStates(); + foreach($stateRemaps as $remap){ + $oldState = $remap->oldState; + $newState = $remap->newState; - if(count($oldState) === 0 || count($newState) === 0){ - //all states have changed in some way - compression not possible - assert(!isset($unchangedStatesByNewName[$remap->new->getName()])); - $compressedRemaps[$remap->new->getName()][] = new BlockStateUpgradeSchemaBlockRemap( - $oldState, - $remap->new->getName(), - $newState, - [] - ); + if($oldState === null || $newState === null){ + //no unchanged states - no compression possible + assert(!isset($unchangedStatesByNewName[$remap->newName])); + $compressedRemaps[$remap->newName][] = $remap; continue; } $cleanedOldState = $oldState; $cleanedNewState = $newState; - foreach($unchangedStatesByNewName[$remap->new->getName()] as $propertyName){ + foreach($unchangedStatesByNewName[$remap->newName] as $propertyName){ unset($cleanedOldState[$propertyName]); unset($cleanedNewState[$propertyName]); } @@ -356,8 +353,10 @@ function processRemappedStates(array $upgradeTable) : array{ ksort($cleanedNewState); $duplicate = false; - $compressedRemaps[$remap->new->getName()] ??= []; - foreach($compressedRemaps[$remap->new->getName()] as $k => $compressedRemap){ + $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) @@ -380,11 +379,11 @@ function processRemappedStates(array $upgradeTable) : array{ break; } if(!$duplicate){ - $compressedRemaps[$remap->new->getName()][] = new BlockStateUpgradeSchemaBlockRemap( + $compressedRemaps[$remap->newName][] = new BlockStateUpgradeSchemaBlockRemap( $cleanedOldState, - $remap->new->getName(), + $remap->newName, $cleanedNewState, - $unchangedStatesByNewName[$remap->new->getName()] + $unchangedStatesByNewName[$remap->newName] ); } } @@ -456,9 +455,21 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad } } //block mapped to multiple different new IDs; we can't guess these, so we just do a plain old remap - $result->remappedStates[$oldName] = processRemappedStates($blockStateMappings); + 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(), + [] + ); + } + } } } + foreach(Utils::stringifyKeys($result->remappedStates) as $oldName => $remap){ + $result->remappedStates[$oldName] = compressRemappedStates($upgradeTable[$oldName], $remap); + } return $result; } From 2b40c1a5be515e05b98e1d3b36735730a8d9a1a1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 26 Jun 2023 16:07:52 +0100 Subject: [PATCH 13/13] Revert "tools/generate-blockstate-upgrade-schema: improve property remapping checks" This reverts commit b8788c55c58effd04f5d8774ae04dd1656813abb. This changes behaviour, so it needs to target minor-next. --- tools/generate-blockstate-upgrade-schema.php | 279 +++++++------------ 1 file changed, 96 insertions(+), 183 deletions(-) diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php index 691f608fad..847486a668 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/generate-blockstate-upgrade-schema.php @@ -28,26 +28,18 @@ use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchema; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaBlockRemap; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaUtils; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap; -use pocketmine\nbt\LittleEndianNbtSerializer; 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_keys; -use function array_map; use function array_merge; -use function array_shift; use function array_values; use function assert; use function count; use function dirname; use function file_put_contents; use function fwrite; -use function implode; use function json_encode; use function ksort; use function usort; @@ -64,22 +56,9 @@ class BlockStateMapping{ ){} } -/** - * @param Tag[] $properties - * @phpstan-param array $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> + * @phpstan-return array> */ function loadUpgradeTable(string $file, bool $reverse) : array{ $contents = Filesystem::fileGetContents($file); @@ -93,7 +72,7 @@ function loadUpgradeTable(string $file, bool $reverse) : array{ $old = BlockStateData::fromNbt($reverse ? $newTag : $oldTag); $new = BlockStateData::fromNbt($reverse ? $oldTag : $newTag); - $result[$old->getName()][encodeOrderedProperties($old->getStates())] = new BlockStateMapping( + $result[$old->getName()][] = new BlockStateMapping( $old, $new ); @@ -103,176 +82,111 @@ function loadUpgradeTable(string $file, bool $reverse) : array{ } /** - * @param BlockStateData[] $states - * @phpstan-param array $states - * - * @return Tag[][] - * @phpstan-return array> + * @param true[] $removedPropertiesCache + * @param Tag[][] $remappedPropertyValuesCache + * @phpstan-param array $removedPropertiesCache + * @phpstan-param array> $remappedPropertyValuesCache */ -function buildStateGroupSchema(array $states) : ?array{ - $first = $states[array_key_first($states)]; +function processState(BlockStateData $old, BlockStateData $new, BlockStateUpgradeSchema $result, array &$removedPropertiesCache, array &$remappedPropertyValuesCache) : void{ - $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; - } - } + //new and old IDs are the same; compare states + $oldName = $old->getName(); - return $properties; -} + $oldStates = $old->getStates(); + $newStates = $new->getStates(); -/** - * @param BlockStateMapping[] $upgradeTable - * @phpstan-param array $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; + $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; } } } - 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; + 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");; } } - } - - $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; + 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"); + } - 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; + $result->addedProperties[$oldName][$propertyAddedName] = $propertyAddedValue; } - - foreach(Utils::stringifyKeys($newPropertyValues) as $rawOldValue => $newPropertyValue){ - if(!$newPropertyValue->equals($oldPropertyValues[$rawOldValue])){ - $remappedPropertyValues[$oldPropertyName][$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; } } }else{ - $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? - } + $result->remappedStates[$oldName][] = new BlockStateUpgradeSchemaBlockRemap( + $oldStates, + $new->getName(), + $newStates, + [] + ); + \GlobalLogger::get()->warning("warning: multiple properties added and removed for $oldName; added full state remap");; } } - - //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; } /** @@ -282,11 +196,8 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra * * @param BlockStateUpgradeSchemaBlockRemap[] $stateRemaps * @param BlockStateMapping[] $upgradeTable - * @phpstan-param list $stateRemaps - * @phpstan-param array $upgradeTable * * @return BlockStateUpgradeSchemaBlockRemap[] - * @phpstan-return list */ function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array{ $unchangedStatesByNewName = []; @@ -400,7 +311,7 @@ function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array /** * @param BlockStateMapping[][] $upgradeTable - * @phpstan-param array> $upgradeTable + * @phpstan-param array> $upgradeTable */ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgradeSchema{ $foundVersion = -1; @@ -432,6 +343,8 @@ 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; @@ -441,15 +354,15 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad if($newName !== $oldName){ $result->renamedIds[$oldName] = array_key_first($newNameFound); } - if(!processStateGroup($oldName, $blockStateMappings, $result)){ - throw new \RuntimeException("States with the same ID should be fully consistent"); + foreach($blockStateMappings as $mapping){ + processState($mapping->old, $mapping->new, $result, $removedPropertiesCache, $remappedPropertyValuesCache); } }else{ if(isset($newNameFound[$oldName])){ //some of the states stayed under the same ID - we can process these as normal states - $stateGroup = array_filter($blockStateMappings, fn(BlockStateMapping $m) => $m->new->getName() === $oldName); - if(processStateGroup($oldName, $stateGroup, $result)){ - foreach(Utils::stringifyKeys($stateGroup) as $k => $mapping){ + foreach($blockStateMappings as $k => $mapping){ + if($mapping->new->getName() === $oldName){ + processState($mapping->old, $mapping->new, $result, $removedPropertiesCache, $remappedPropertyValuesCache); unset($blockStateMappings[$k]); } }