mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-03 18:42:37 +00:00
Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/11652590245
This commit is contained in:
commit
82c5a3160c
103
changelogs/5.21.md
Normal file
103
changelogs/5.21.md
Normal file
@ -0,0 +1,103 @@
|
||||
# 5.21.0
|
||||
Released 3rd November 2024.
|
||||
|
||||
This is a minor feature release, including gameplay features and minor internals improvements.
|
||||
|
||||
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
|
||||
Do not update plugin minimum API versions unless you need new features added in this release.
|
||||
|
||||
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||
|
||||
## Gameplay
|
||||
- Added the following new blocks:
|
||||
- Campfire
|
||||
- Chiseled Copper
|
||||
- Chiseled Tuff
|
||||
- Chiseled Tuff Bricks
|
||||
- Copper Bulb
|
||||
- Copper Door
|
||||
- Copper Grate
|
||||
- Copper Trapdoor
|
||||
- Polished Tuff, Slabs, Stairs and Walls
|
||||
- Soul Campfire
|
||||
- Tuff Bricks, Slabs, Stairs and Walls
|
||||
- Tuff Slab, Stairs and Walls
|
||||
- Added the following new types of painting:
|
||||
- backyard
|
||||
- baroque
|
||||
- bouquet
|
||||
- cavebird
|
||||
- changing
|
||||
- cotan
|
||||
- endboss
|
||||
- fern
|
||||
- finding
|
||||
- humble
|
||||
- lowmist
|
||||
- meditative
|
||||
- orb
|
||||
- owlemons
|
||||
- passage
|
||||
- pond
|
||||
- prairie_ride
|
||||
- sunflowers
|
||||
- tides
|
||||
- unpacked
|
||||
- Armor slots are now properly restricted (on the server side) to only contain the appropriate type of armor or headwear.
|
||||
- Implemented Aqua Affinity enchantment. Since the server doesn't currently enforce any movement restrictions in water, this enchantment works based on client-side behaviour only.
|
||||
|
||||
## API
|
||||
### `pocketmine\block`
|
||||
- The following new API methods have been added:
|
||||
- `public ChiseledBookshelf->getLastInteractedSlot() : ?ChiseledBookshelfSlot`
|
||||
- `public ChiseledBookshelf->setLastInteractedSlot(?ChiseledBookshelfSlot $lastInteractedSlot) : $this`
|
||||
- The following new classes have been added:
|
||||
- `utils\CopperMaterial` - interface implemented by all copper-like blocks with oxidation and waxed properties
|
||||
- `CopperBulb`
|
||||
- `CopperDoor`
|
||||
- `CopperGrate`
|
||||
- `CopperTrapdoor`
|
||||
- `SoulCampfire`
|
||||
- `Campfire`
|
||||
- The following enums have new cases:
|
||||
- `utils\BannerPatternType` has new cases `FLOW` and `GUSTER`
|
||||
|
||||
### `pocketmine\crafting`
|
||||
- The following enums have new cases:
|
||||
- `FurnaceType` has new cases `CAMPFIRE` and `SOUL_CAMPFIRE`
|
||||
|
||||
### `pocketmine\event`
|
||||
- The following new classes have been added:
|
||||
- `block\CampfireCookEvent` - called when a campfire finishes cooking an item
|
||||
|
||||
### `pocketmine\inventory`
|
||||
- Added support for slot validators, which permit restricting the types of items a player can put into an inventory slot.
|
||||
- The following new classes have been added:
|
||||
- `transaction\action\SlotValidator` - interface
|
||||
- `transaction\action\CallbackSlotValidator` - class allowing a closure to be used for slot content validation
|
||||
- `SlotValidatedInventory` - implemented by inventories which support the use of slot validators
|
||||
|
||||
### `pocketmine\item`
|
||||
- The following new API methods have been added:
|
||||
- `public Item->getCooldownTag() : ?string` - returns the cooldown group this item belongs to, used for ensuring that, for example, different types of goat horns all respect a general horn cooldown
|
||||
- The following new classes have been added:
|
||||
- `ItemCooldownTags` - list of cooldown group tags used by PocketMine-MP
|
||||
|
||||
### `pocketmine\world\sound`
|
||||
- The following new classes have been added
|
||||
- `CampfireSound` - sound made by campfires while lit
|
||||
|
||||
## Tools
|
||||
- `tools/blockstate-upgrade-schema-utils.php` (formerly `generate-blockstate-upgrade-schema.php`) has several improvements:
|
||||
- Support for generating `flattenedProperties` rules as per [BedrockBlockUpgradeSchema 5.0.0](https://github.com/pmmp/BedrockBlockUpgradeSchema/releases/tag/5.0.0)
|
||||
- Improved criteria for flattened property selection to minimize the amount of rules required
|
||||
- Several subcommands are now available:
|
||||
- `generate` - generates a schema from provided data
|
||||
- `update` - regenerates an existing schema in a newer format
|
||||
- `update-all` - regenerates a folder of existing schemas in a newer format (useful for updating `BedrockBlockUpgradeSchema` en masse)
|
||||
- `test` - verifies that a schema produces the results expected by provided data
|
||||
|
||||
## Internals
|
||||
- Fixed incorrect visibility of `createEntity` in spawn eggs.
|
||||
- Added support for newer `BedrockBlockUpgradeSchema` in `BlockStateUpgrader`.
|
@ -33,7 +33,7 @@
|
||||
"composer-runtime-api": "^2.0",
|
||||
"adhocore/json-comment": "~1.2.0",
|
||||
"pocketmine/netresearch-jsonmapper": "~v4.4.999",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~4.5.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-block-upgrade-schema": "~5.0.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-data": "~2.14.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-item-upgrade-schema": "~1.13.0+bedrock-1.21.40",
|
||||
"pocketmine/bedrock-protocol": "~35.0.0+bedrock-1.21.40",
|
||||
|
14
composer.lock
generated
14
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "5c5882370131d2ae3a043819c05e6f9c",
|
||||
"content-hash": "b2fbf6e7a9d650341dc71fa4dd124681",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -127,16 +127,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-block-upgrade-schema",
|
||||
"version": "4.5.0",
|
||||
"version": "5.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
|
||||
"reference": "7943b894e050d68dd21b5c7fa609827a4e2e30f1"
|
||||
"reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/7943b894e050d68dd21b5c7fa609827a4e2e30f1",
|
||||
"reference": "7943b894e050d68dd21b5c7fa609827a4e2e30f1",
|
||||
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
|
||||
"reference": "20dd5c11e9915bacea4fe2cf649e1d23697a6e52",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -147,9 +147,9 @@
|
||||
"description": "Schemas describing how to upgrade saved block data in older Minecraft: Bedrock Edition world saves",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/BedrockBlockUpgradeSchema/issues",
|
||||
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/4.5.0"
|
||||
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/5.0.0"
|
||||
},
|
||||
"time": "2024-10-23T16:15:24+00:00"
|
||||
"time": "2024-11-03T14:13:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/bedrock-data",
|
||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "5.20.2";
|
||||
public const BASE_VERSION = "5.21.1";
|
||||
public const IS_DEVELOPMENT_BUILD = true;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\data\bedrock\block\upgrade;
|
||||
|
||||
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenInfo as FlattenInfo;
|
||||
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap as ValueRemap;
|
||||
use pocketmine\nbt\tag\Tag;
|
||||
use function count;
|
||||
@ -58,6 +59,12 @@ final class BlockStateUpgradeSchema{
|
||||
*/
|
||||
public array $remappedPropertyValues = [];
|
||||
|
||||
/**
|
||||
* @var FlattenInfo[]
|
||||
* @phpstan-var array<string, FlattenInfo>
|
||||
*/
|
||||
public array $flattenedProperties = [];
|
||||
|
||||
/**
|
||||
* @var BlockStateUpgradeSchemaBlockRemap[][]
|
||||
* @phpstan-var array<string, list<BlockStateUpgradeSchemaBlockRemap>>
|
||||
@ -93,6 +100,7 @@ final class BlockStateUpgradeSchema{
|
||||
$this->removedProperties,
|
||||
$this->renamedProperties,
|
||||
$this->remappedPropertyValues,
|
||||
$this->flattenedProperties,
|
||||
$this->remappedStates,
|
||||
] as $list){
|
||||
if(count($list) !== 0){
|
||||
|
@ -40,7 +40,7 @@ final class BlockStateUpgradeSchemaBlockRemap{
|
||||
*/
|
||||
public function __construct(
|
||||
public array $oldState,
|
||||
public string|BlockStateUpgradeSchemaFlattenedName $newName,
|
||||
public string|BlockStateUpgradeSchemaFlattenInfo $newName,
|
||||
public array $newState,
|
||||
public array $copiedState
|
||||
){}
|
||||
@ -48,8 +48,8 @@ final class BlockStateUpgradeSchemaBlockRemap{
|
||||
public function equals(self $that) : bool{
|
||||
$sameName = $this->newName === $that->newName ||
|
||||
(
|
||||
$this->newName instanceof BlockStateUpgradeSchemaFlattenedName &&
|
||||
$that->newName instanceof BlockStateUpgradeSchemaFlattenedName &&
|
||||
$this->newName instanceof BlockStateUpgradeSchemaFlattenInfo &&
|
||||
$that->newName instanceof BlockStateUpgradeSchemaFlattenInfo &&
|
||||
$this->newName->equals($that->newName)
|
||||
);
|
||||
if(!$sameName){
|
||||
|
@ -23,20 +23,25 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\data\bedrock\block\upgrade;
|
||||
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use function ksort;
|
||||
use const SORT_STRING;
|
||||
|
||||
final class BlockStateUpgradeSchemaFlattenedName{
|
||||
final class BlockStateUpgradeSchemaFlattenInfo{
|
||||
|
||||
/**
|
||||
* @param string[] $flattenedValueRemaps
|
||||
* @phpstan-param array<string, string> $flattenedValueRemaps
|
||||
* @phpstan-param ?class-string<ByteTag|IntTag|StringTag> $flattenedPropertyType
|
||||
*/
|
||||
public function __construct(
|
||||
public string $prefix,
|
||||
public string $flattenedProperty,
|
||||
public string $suffix,
|
||||
public array $flattenedValueRemaps
|
||||
public array $flattenedValueRemaps,
|
||||
public ?string $flattenedPropertyType = null
|
||||
){
|
||||
ksort($this->flattenedValueRemaps, SORT_STRING);
|
||||
}
|
||||
@ -45,6 +50,7 @@ final class BlockStateUpgradeSchemaFlattenedName{
|
||||
return $this->prefix === $that->prefix &&
|
||||
$this->flattenedProperty === $that->flattenedProperty &&
|
||||
$this->suffix === $that->suffix &&
|
||||
$this->flattenedValueRemaps === $that->flattenedValueRemaps;
|
||||
$this->flattenedValueRemaps === $that->flattenedValueRemaps &&
|
||||
$this->flattenedPropertyType === $that->flattenedPropertyType;
|
||||
}
|
||||
}
|
@ -25,7 +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\BlockStateUpgradeSchemaModelFlattenInfo;
|
||||
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelTag;
|
||||
use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelValueRemap;
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
@ -155,20 +155,24 @@ final class BlockStateUpgradeSchemaUtils{
|
||||
}
|
||||
}
|
||||
|
||||
foreach(Utils::stringifyKeys($model->flattenedProperties ?? []) as $blockName => $flattenRule){
|
||||
$result->flattenedProperties[$blockName] = self::jsonModelToFlattenRule($flattenRule);
|
||||
}
|
||||
|
||||
foreach(Utils::stringifyKeys($model->remappedStates ?? []) as $oldBlockName => $remaps){
|
||||
foreach($remaps as $remap){
|
||||
if(isset($remap->newName) === isset($remap->newFlattenedName)){
|
||||
if(isset($remap->newName)){
|
||||
$remapName = $remap->newName;
|
||||
}elseif(isset($remap->newFlattenedName)){
|
||||
$flattenRule = $remap->newFlattenedName;
|
||||
$remapName = self::jsonModelToFlattenRule($flattenRule);
|
||||
}else{
|
||||
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 ?? new BlockStateUpgradeSchemaFlattenedName(
|
||||
$remap->newFlattenedName->prefix,
|
||||
$remap->newFlattenedName->flattenedProperty,
|
||||
$remap->newFlattenedName->suffix,
|
||||
$remap->newFlattenedName->flattenedValueRemaps ?? [],
|
||||
),
|
||||
$remapName,
|
||||
array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->newState ?? []),
|
||||
$remap->copiedState ?? []
|
||||
);
|
||||
@ -254,6 +258,36 @@ final class BlockStateUpgradeSchemaUtils{
|
||||
$model->remappedPropertyValues = $modelDedupMapping;
|
||||
}
|
||||
|
||||
private static function flattenRuleToJsonModel(BlockStateUpgradeSchemaFlattenInfo $flattenRule) : BlockStateUpgradeSchemaModelFlattenInfo{
|
||||
return new BlockStateUpgradeSchemaModelFlattenInfo(
|
||||
$flattenRule->prefix,
|
||||
$flattenRule->flattenedProperty,
|
||||
$flattenRule->suffix,
|
||||
$flattenRule->flattenedValueRemaps,
|
||||
match($flattenRule->flattenedPropertyType){
|
||||
StringTag::class => null, //omit for TAG_String, as this is the common case
|
||||
ByteTag::class => "byte",
|
||||
IntTag::class => "int",
|
||||
default => throw new \LogicException("Unexpected tag type " . $flattenRule->flattenedPropertyType . " in flattened property type")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static function jsonModelToFlattenRule(BlockStateUpgradeSchemaModelFlattenInfo $flattenRule) : BlockStateUpgradeSchemaFlattenInfo{
|
||||
return new BlockStateUpgradeSchemaFlattenInfo(
|
||||
$flattenRule->prefix,
|
||||
$flattenRule->flattenedProperty,
|
||||
$flattenRule->suffix,
|
||||
$flattenRule->flattenedValueRemaps ?? [],
|
||||
match ($flattenRule->flattenedPropertyType) {
|
||||
"string", null => StringTag::class,
|
||||
"int" => IntTag::class,
|
||||
"byte" => ByteTag::class,
|
||||
default => throw new \UnexpectedValueException("Unexpected flattened property type $flattenRule->flattenedPropertyType, expected 'string', 'int' or 'byte'")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static function toJsonModel(BlockStateUpgradeSchema $schema) : BlockStateUpgradeSchemaModel{
|
||||
$result = new BlockStateUpgradeSchemaModel();
|
||||
$result->maxVersionMajor = $schema->maxVersionMajor;
|
||||
@ -292,19 +326,19 @@ final class BlockStateUpgradeSchemaUtils{
|
||||
|
||||
self::buildRemappedValuesIndex($schema, $result);
|
||||
|
||||
foreach(Utils::stringifyKeys($schema->flattenedProperties) as $blockName => $flattenRule){
|
||||
$result->flattenedProperties[$blockName] = self::flattenRuleToJsonModel($flattenRule);
|
||||
}
|
||||
if(isset($result->flattenedProperties)){
|
||||
ksort($result->flattenedProperties);
|
||||
}
|
||||
|
||||
foreach(Utils::stringifyKeys($schema->remappedStates) as $oldBlockName => $remaps){
|
||||
$keyedRemaps = [];
|
||||
foreach($remaps as $remap){
|
||||
$modelRemap = new BlockStateUpgradeSchemaModelBlockRemap(
|
||||
array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->oldState),
|
||||
is_string($remap->newName) ?
|
||||
$remap->newName :
|
||||
new BlockStateUpgradeSchemaModelFlattenedName(
|
||||
$remap->newName->prefix,
|
||||
$remap->newName->flattenedProperty,
|
||||
$remap->newName->suffix,
|
||||
$remap->newName->flattenedValueRemaps
|
||||
),
|
||||
is_string($remap->newName) ? $remap->newName : self::flattenRuleToJsonModel($remap->newName),
|
||||
array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->newState),
|
||||
$remap->copiedState
|
||||
);
|
||||
|
@ -24,10 +24,14 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock\block\upgrade;
|
||||
|
||||
use pocketmine\data\bedrock\block\BlockStateData;
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\nbt\tag\Tag;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function is_string;
|
||||
use function ksort;
|
||||
use function max;
|
||||
@ -79,6 +83,8 @@ final class BlockStateUpgrader{
|
||||
* version doesn't tell us which of the schemas have already been applied.
|
||||
* If there's only one schema for a version (the norm), we can safely assume it's already been applied if
|
||||
* the version is the same, and skip over it.
|
||||
* TODO: this causes issues when testing isolated schemas since there will only be one schema for a version.
|
||||
* The second check should be disabled for that case.
|
||||
*/
|
||||
if($version > $resultVersion || (count($schemaList) === 1 && $version === $resultVersion)){
|
||||
continue;
|
||||
@ -104,10 +110,21 @@ final class BlockStateUpgrader{
|
||||
}
|
||||
|
||||
$oldName = $blockStateData->getName();
|
||||
$newName = $schema->renamedIds[$oldName] ?? null;
|
||||
$states = $blockStateData->getStates();
|
||||
|
||||
if(isset($schema->renamedIds[$oldName]) && isset($schema->flattenedProperties[$oldName])){
|
||||
//TODO: this probably ought to be validated when the schema is constructed
|
||||
throw new AssumptionFailedError("Both renamedIds and flattenedProperties are set for the same block ID \"$oldName\" - don't know what to do");
|
||||
}
|
||||
if(isset($schema->renamedIds[$oldName])){
|
||||
$newName = $schema->renamedIds[$oldName] ?? null;
|
||||
}elseif(isset($schema->flattenedProperties[$oldName])){
|
||||
[$newName, $states] = $this->applyPropertyFlattened($schema->flattenedProperties[$oldName], $oldName, $states);
|
||||
}else{
|
||||
$newName = null;
|
||||
}
|
||||
|
||||
$stateChanges = 0;
|
||||
$states = $blockStateData->getStates();
|
||||
|
||||
$states = $this->applyPropertyAdded($schema, $oldName, $states, $stateChanges);
|
||||
$states = $this->applyPropertyRemoved($schema, $oldName, $states, $stateChanges);
|
||||
@ -140,15 +157,8 @@ final class BlockStateUpgrader{
|
||||
if(is_string($remap->newName)){
|
||||
$newName = $remap->newName;
|
||||
}else{
|
||||
$flattenedValue = $oldState[$remap->newName->flattenedProperty] ?? null;
|
||||
if($flattenedValue instanceof StringTag){
|
||||
$embedValue = $remap->newName->flattenedValueRemaps[$flattenedValue->getValue()] ?? $flattenedValue->getValue();
|
||||
$newName = sprintf("%s%s%s", $remap->newName->prefix, $embedValue, $remap->newName->suffix);
|
||||
unset($oldState[$remap->newName->flattenedProperty]);
|
||||
}else{
|
||||
//flattened property is not a TAG_String, so this transformation is not applicable
|
||||
continue;
|
||||
}
|
||||
//discard flatten modifications to state - the remap newState and copiedState will take care of it
|
||||
[$newName, ] = $this->applyPropertyFlattened($remap->newName, $oldName, $oldState);
|
||||
}
|
||||
|
||||
$newState = $remap->newState;
|
||||
@ -266,4 +276,32 @@ final class BlockStateUpgrader{
|
||||
|
||||
return $states;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tag[] $states
|
||||
* @phpstan-param array<string, Tag> $states
|
||||
*
|
||||
* @return (string|Tag[])[]
|
||||
* @phpstan-return array{0: string, 1: array<string, Tag>}
|
||||
*/
|
||||
private function applyPropertyFlattened(BlockStateUpgradeSchemaFlattenInfo $flattenInfo, string $oldName, array $states) : array{
|
||||
$flattenedValue = $states[$flattenInfo->flattenedProperty] ?? null;
|
||||
$expectedType = $flattenInfo->flattenedPropertyType;
|
||||
if(!$flattenedValue instanceof $expectedType){
|
||||
//flattened property is not of the expected type, so this transformation is not applicable
|
||||
return [$oldName, $states];
|
||||
}
|
||||
$embedKey = match(get_class($flattenedValue)){
|
||||
StringTag::class => $flattenedValue->getValue(),
|
||||
ByteTag::class => (string) $flattenedValue->getValue(),
|
||||
IntTag::class => (string) $flattenedValue->getValue(),
|
||||
//flattenedPropertyType is always one of these three types, but PHPStan doesn't know that
|
||||
default => throw new AssumptionFailedError("flattenedPropertyType should be one of these three types, but have " . get_class($flattenedValue)),
|
||||
};
|
||||
$embedValue = $flattenInfo->flattenedValueRemaps[$embedKey] ?? $embedKey;
|
||||
$newName = sprintf("%s%s%s", $flattenInfo->prefix, $embedValue, $flattenInfo->suffix);
|
||||
unset($states[$flattenInfo->flattenedProperty]);
|
||||
|
||||
return [$newName, $states];
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +75,12 @@ final class BlockStateUpgradeSchemaModel implements \JsonSerializable{
|
||||
*/
|
||||
public array $remappedPropertyValuesIndex;
|
||||
|
||||
/**
|
||||
* @var BlockStateUpgradeSchemaModelFlattenInfo[]
|
||||
* @phpstan-var array<string, BlockStateUpgradeSchemaModelFlattenInfo>
|
||||
*/
|
||||
public array $flattenedProperties;
|
||||
|
||||
/**
|
||||
* @var BlockStateUpgradeSchemaModelBlockRemap[][]
|
||||
* @phpstan-var array<string, list<BlockStateUpgradeSchemaModelBlockRemap>>
|
||||
|
@ -43,7 +43,7 @@ final class BlockStateUpgradeSchemaModelBlockRemap{
|
||||
* Either this or newName must be present
|
||||
* Due to technical limitations of jsonmapper, we can't use a union type here
|
||||
*/
|
||||
public BlockStateUpgradeSchemaModelFlattenedName $newFlattenedName;
|
||||
public BlockStateUpgradeSchemaModelFlattenInfo $newFlattenedName;
|
||||
|
||||
/**
|
||||
* @var BlockStateUpgradeSchemaModelTag[]|null
|
||||
@ -67,9 +67,9 @@ final class BlockStateUpgradeSchemaModelBlockRemap{
|
||||
* @phpstan-param array<string, BlockStateUpgradeSchemaModelTag> $newState
|
||||
* @phpstan-param list<string> $copiedState
|
||||
*/
|
||||
public function __construct(array $oldState, string|BlockStateUpgradeSchemaModelFlattenedName $newNameRule, array $newState, array $copiedState){
|
||||
public function __construct(array $oldState, string|BlockStateUpgradeSchemaModelFlattenInfo $newNameRule, array $newState, array $copiedState){
|
||||
$this->oldState = count($oldState) === 0 ? null : $oldState;
|
||||
if($newNameRule instanceof BlockStateUpgradeSchemaModelFlattenedName){
|
||||
if($newNameRule instanceof BlockStateUpgradeSchemaModelFlattenInfo){
|
||||
$this->newFlattenedName = $newNameRule;
|
||||
}else{
|
||||
$this->newName = $newNameRule;
|
||||
|
@ -25,12 +25,13 @@ namespace pocketmine\data\bedrock\block\upgrade\model;
|
||||
|
||||
use function count;
|
||||
|
||||
final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializable{
|
||||
final class BlockStateUpgradeSchemaModelFlattenInfo implements \JsonSerializable{
|
||||
|
||||
/** @required */
|
||||
public string $prefix;
|
||||
/** @required */
|
||||
public string $flattenedProperty;
|
||||
public ?string $flattenedPropertyType = null;
|
||||
/** @required */
|
||||
public string $suffix;
|
||||
/**
|
||||
@ -43,11 +44,12 @@ final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializab
|
||||
* @param string[] $flattenedValueRemaps
|
||||
* @phpstan-param array<string, string> $flattenedValueRemaps
|
||||
*/
|
||||
public function __construct(string $prefix, string $flattenedProperty, string $suffix, array $flattenedValueRemaps){
|
||||
public function __construct(string $prefix, string $flattenedProperty, string $suffix, array $flattenedValueRemaps, ?string $flattenedPropertyType = null){
|
||||
$this->prefix = $prefix;
|
||||
$this->flattenedProperty = $flattenedProperty;
|
||||
$this->suffix = $suffix;
|
||||
$this->flattenedValueRemaps = $flattenedValueRemaps;
|
||||
$this->flattenedPropertyType = $flattenedPropertyType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,6 +60,9 @@ final class BlockStateUpgradeSchemaModelFlattenedName implements \JsonSerializab
|
||||
if(count($this->flattenedValueRemaps) === 0){
|
||||
unset($result["flattenedValueRemaps"]);
|
||||
}
|
||||
if($this->flattenedPropertyType === null){
|
||||
unset($result["flattenedPropertyType"]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -364,6 +364,7 @@ class InventoryManager{
|
||||
FurnaceType::FURNACE => WindowTypes::FURNACE,
|
||||
FurnaceType::BLAST_FURNACE => WindowTypes::BLAST_FURNACE,
|
||||
FurnaceType::SMOKER => WindowTypes::SMOKER,
|
||||
FurnaceType::CAMPFIRE, FurnaceType::SOUL_CAMPFIRE => throw new \LogicException("Campfire inventory cannot be displayed to a player")
|
||||
},
|
||||
$inv instanceof EnchantInventory => WindowTypes::ENCHANTMENT,
|
||||
$inv instanceof BrewingStandInventory => WindowTypes::BREWING_STAND,
|
||||
|
@ -580,11 +580,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/ChunkRequestTask.php
|
||||
|
||||
-
|
||||
message: "#^Match expression does not handle remaining values\\: pocketmine\\\\crafting\\\\FurnaceType\\:\\:CAMPFIRE\\|pocketmine\\\\crafting\\\\FurnaceType\\:\\:SOUL_CAMPFIRE$#"
|
||||
count: 1
|
||||
path: ../../../src/network/mcpe/InventoryManager.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method doFirstSpawn\\(\\) on pocketmine\\\\player\\\\Player\\|null\\.$#"
|
||||
count: 1
|
||||
@ -790,11 +785,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/resourcepacks/ZippedResourcePack.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/resourcepacks/ZippedResourcePack.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\resourcepacks\\\\ZippedResourcePack\\:\\:\\$fileResource \\(resource\\) does not accept resource\\|false\\.$#"
|
||||
count: 1
|
||||
@ -1040,11 +1030,6 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/world/format/io/region/RegionLoader.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$length of function fread expects int\\<0, max\\>, int given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/world/format/io/region/RegionLoader.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$size of function ftruncate expects int\\<0, max\\>, int given\\.$#"
|
||||
count: 1
|
||||
@ -1195,18 +1180,3 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/world/light/SkyLightUpdate.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: ../../phpunit/block/BlockTest.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: ../../phpunit/block/regenerate_consistency_check.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$logFile of class pocketmine\\\\utils\\\\MainLogger constructor expects string, string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: ../../phpunit/scheduler/AsyncPoolTest.php
|
||||
|
||||
|
@ -5,20 +5,20 @@ parameters:
|
||||
count: 1
|
||||
path: ../../../src/block/CakeWithCandle.php
|
||||
|
||||
-
|
||||
message: "#^Method pocketmine\\\\block\\\\DoubleTallGrass\\:\\:traitGetDropsForIncompatibleTool\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/block/DoubleTallGrass.php
|
||||
|
||||
-
|
||||
message: "#^Method pocketmine\\\\block\\\\CopperDoor\\:\\:onInteractCopper\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/block/utils/CopperTrait.php
|
||||
path: ../../../src/block/CopperDoor.php
|
||||
|
||||
-
|
||||
message: "#^Method pocketmine\\\\block\\\\CopperTrapdoor\\:\\:onInteractCopper\\(\\) has parameter \\$returnedItems with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/block/utils/CopperTrait.php
|
||||
path: ../../../src/block/CopperTrapdoor.php
|
||||
|
||||
-
|
||||
message: "#^Method pocketmine\\\\block\\\\DoubleTallGrass\\:\\:traitGetDropsForIncompatibleTool\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/block/DoubleTallGrass.php
|
||||
|
||||
-
|
||||
message: "#^Call to function assert\\(\\) with false and 'unknown hit type' will always evaluate to false\\.$#"
|
||||
|
@ -24,8 +24,10 @@ declare(strict_types=1);
|
||||
namespace pocketmine\data\bedrock\block\upgrade;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\data\bedrock\block\BlockStateData;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
class BlockStateUpgraderTest extends TestCase{
|
||||
@ -210,6 +212,23 @@ class BlockStateUpgraderTest extends TestCase{
|
||||
self::assertSame($upgradedStateData->getState(self::TEST_PROPERTY_2)?->getValue(), $valueAfter);
|
||||
}
|
||||
|
||||
public function testFlattenProperty() : void{
|
||||
$schema = $this->getNewSchema();
|
||||
$schema->flattenedProperties[self::TEST_BLOCK] = new BlockStateUpgradeSchemaFlattenInfo(
|
||||
"minecraft:",
|
||||
"test",
|
||||
"_suffix",
|
||||
[],
|
||||
StringTag::class
|
||||
);
|
||||
|
||||
$stateData = new BlockStateData(self::TEST_BLOCK, ["test" => new StringTag("value1")], 0);
|
||||
$upgradedStateData = $this->upgrade($stateData, fn() => $stateData);
|
||||
|
||||
self::assertSame("minecraft:value1_suffix", $upgradedStateData->getName());
|
||||
self::assertEmpty($upgradedStateData->getStates());
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return \Generator<int, array{int, int, bool, int}, void, void>
|
||||
*/
|
||||
|
@ -21,36 +21,49 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\tools\generate_blockstate_upgrade_schema;
|
||||
namespace pocketmine\tools\blockstate_upgrade_schema_utils;
|
||||
|
||||
use pocketmine\data\bedrock\block\BlockStateData;
|
||||
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgrader;
|
||||
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\BlockStateUpgradeSchemaFlattenInfo;
|
||||
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaUtils;
|
||||
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap;
|
||||
use pocketmine\nbt\LittleEndianNbtSerializer;
|
||||
use pocketmine\nbt\tag\ByteTag;
|
||||
use pocketmine\nbt\tag\IntTag;
|
||||
use pocketmine\nbt\tag\StringTag;
|
||||
use pocketmine\nbt\tag\Tag;
|
||||
use pocketmine\nbt\TreeRoot;
|
||||
use pocketmine\network\mcpe\convert\BlockStateDictionary;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function array_key_first;
|
||||
use function array_key_last;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_shift;
|
||||
use function array_unique;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function file_exists;
|
||||
use function file_put_contents;
|
||||
use function fwrite;
|
||||
use function get_class;
|
||||
use function get_debug_type;
|
||||
use function implode;
|
||||
use function is_dir;
|
||||
use function is_numeric;
|
||||
use function json_encode;
|
||||
use function ksort;
|
||||
use function min;
|
||||
use function preg_match;
|
||||
use function scandir;
|
||||
use function sort;
|
||||
use function strlen;
|
||||
use function strrev;
|
||||
@ -83,18 +96,18 @@ function encodeProperty(Tag $tag) : string{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TreeRoot[] $oldNewStateList
|
||||
* @phpstan-param list<TreeRoot> $oldNewStateList
|
||||
*
|
||||
* @return BlockStateMapping[][]
|
||||
* @phpstan-return array<string, array<string, BlockStateMapping>>
|
||||
*/
|
||||
function loadUpgradeTable(string $file, bool $reverse) : array{
|
||||
$contents = Filesystem::fileGetContents($file);
|
||||
$data = (new NetworkNbtSerializer())->readMultiple($contents);
|
||||
|
||||
function buildUpgradeTableFromData(array $oldNewStateList, bool $reverse) : array{
|
||||
$result = [];
|
||||
|
||||
for($i = 0; isset($data[$i]); $i += 2){
|
||||
$oldTag = $data[$i]->mustGetCompoundTag();
|
||||
$newTag = $data[$i + 1]->mustGetCompoundTag();
|
||||
for($i = 0; isset($oldNewStateList[$i]); $i += 2){
|
||||
$oldTag = $oldNewStateList[$i]->mustGetCompoundTag();
|
||||
$newTag = $oldNewStateList[$i + 1]->mustGetCompoundTag();
|
||||
$old = BlockStateData::fromNbt($reverse ? $newTag : $oldTag);
|
||||
$new = BlockStateData::fromNbt($reverse ? $oldTag : $newTag);
|
||||
|
||||
@ -107,6 +120,17 @@ function loadUpgradeTable(string $file, bool $reverse) : array{
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BlockStateMapping[][]
|
||||
* @phpstan-return array<string, array<string, BlockStateMapping>>
|
||||
*/
|
||||
function loadUpgradeTableFromFile(string $file, bool $reverse) : array{
|
||||
$contents = Filesystem::fileGetContents($file);
|
||||
$data = (new NetworkNbtSerializer())->readMultiple($contents);
|
||||
|
||||
return buildUpgradeTableFromData($data, $reverse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BlockStateData[] $states
|
||||
* @phpstan-param array<string, BlockStateData> $states
|
||||
@ -159,6 +183,11 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra
|
||||
$removedProperties = [];
|
||||
$renamedProperties = [];
|
||||
|
||||
$uniqueNewIds = [];
|
||||
foreach($upgradeTable as $pair){
|
||||
$uniqueNewIds[$pair->new->getName()] = $pair->new->getName();
|
||||
}
|
||||
|
||||
foreach(Utils::stringifyKeys($newProperties) as $newPropertyName => $newPropertyValues){
|
||||
if(count($newPropertyValues) === 1){
|
||||
$newPropertyValue = $newPropertyValues[array_key_first($newPropertyValues)];
|
||||
@ -254,6 +283,45 @@ function processStateGroup(string $oldName, array $upgradeTable, BlockStateUpgra
|
||||
}
|
||||
}
|
||||
|
||||
if(count($uniqueNewIds) > 1){
|
||||
//detect possible flattening
|
||||
$flattenedProperty = null;
|
||||
$flattenedPropertyType = null;
|
||||
$flattenedPropertyMap = [];
|
||||
foreach($removedProperties as $removedProperty){
|
||||
$valueMap = [];
|
||||
foreach($upgradeTable as $pair){
|
||||
$oldValue = $pair->old->getState($removedProperty);
|
||||
if($oldValue === null){
|
||||
throw new AssumptionFailedError("We already checked that all states had consistent old properties");
|
||||
}
|
||||
if(!checkFlattenPropertySuitability($oldValue, $flattenedPropertyType, $pair->new->getName(), $valueMap)){
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if($flattenedProperty !== null){
|
||||
//found multiple candidates for flattening - fallback to remappedStates
|
||||
return false;
|
||||
}
|
||||
//we found a suitable candidate
|
||||
$flattenedProperty = $removedProperty;
|
||||
$flattenedPropertyMap = $valueMap;
|
||||
break;
|
||||
}
|
||||
|
||||
if($flattenedProperty === null){
|
||||
//can't figure out how the new IDs are related to the old states - fallback to remappedStates
|
||||
return false;
|
||||
}
|
||||
if($flattenedPropertyType === null){
|
||||
throw new AssumptionFailedError("This should never happen at this point");
|
||||
}
|
||||
|
||||
$result->flattenedProperties[$oldName] = buildFlattenPropertyRule($flattenedPropertyMap, $flattenedProperty, $flattenedPropertyType);
|
||||
unset($removedProperties[$flattenedProperty]);
|
||||
}
|
||||
|
||||
//finally, write the results to the schema
|
||||
|
||||
if(count($remappedPropertyValues) !== 0){
|
||||
@ -308,43 +376,100 @@ function findCommonSuffix(array $strings) : string{
|
||||
return strrev(findCommonPrefix($reversed));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $valueToIdMap
|
||||
* @phpstan-param ?class-string<ByteTag|IntTag|StringTag> $expectedType
|
||||
* @phpstan-param-out class-string<ByteTag|IntTag|StringTag> $expectedType
|
||||
* @phpstan-param array<string, string> $valueToIdMap
|
||||
* @phpstan-param-out array<string, string> $valueToIdMap
|
||||
*/
|
||||
function checkFlattenPropertySuitability(Tag $oldValue, ?string &$expectedType, string $actualNewId, array &$valueToIdMap) : bool{
|
||||
//TODO: lots of similar logic to the remappedStates builder below
|
||||
if(!$oldValue instanceof ByteTag && !$oldValue instanceof IntTag && !$oldValue instanceof StringTag){
|
||||
//unknown property type - bad candidate for flattening
|
||||
return false;
|
||||
}
|
||||
if($expectedType === null){
|
||||
$expectedType = get_class($oldValue);
|
||||
}elseif(!$oldValue instanceof $expectedType){
|
||||
//property type mismatch - bad candidate for flattening
|
||||
return false;
|
||||
}
|
||||
|
||||
$rawValue = (string) $oldValue->getValue();
|
||||
$existingNewId = $valueToIdMap[$rawValue] ?? null;
|
||||
if($existingNewId !== null && $existingNewId !== $actualNewId){
|
||||
//this property value is associated with multiple new IDs - bad candidate for flattening
|
||||
return false;
|
||||
}
|
||||
$valueToIdMap[$rawValue] = $actualNewId;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $valueToId
|
||||
* @phpstan-param array<string, string> $valueToId
|
||||
* @phpstan-param class-string<ByteTag|IntTag|StringTag> $propertyType
|
||||
*/
|
||||
function buildFlattenPropertyRule(array $valueToId, string $propertyName, string $propertyType) : BlockStateUpgradeSchemaFlattenInfo{
|
||||
$ids = array_values($valueToId);
|
||||
|
||||
//TODO: this is a bit too enthusiastic. For example, when flattening the old "stone", it will see that
|
||||
//"granite", "andesite", "stone" etc all have "e" as a common suffix, which works, but looks a bit daft.
|
||||
//This also causes more remaps to be generated than necessary, since some of the values are already
|
||||
//contained in the new ID.
|
||||
$idPrefix = findCommonPrefix($ids);
|
||||
$idSuffix = findCommonSuffix($ids);
|
||||
if(strlen($idSuffix) < 2){
|
||||
$idSuffix = "";
|
||||
}
|
||||
|
||||
$valueMap = [];
|
||||
foreach(Utils::stringifyKeys($valueToId) as $value => $newId){
|
||||
$newValue = substr($newId, strlen($idPrefix), $idSuffix !== "" ? -strlen($idSuffix) : null);
|
||||
if($newValue !== $value){
|
||||
$valueMap[$value] = $newValue;
|
||||
}
|
||||
}
|
||||
|
||||
$allNumeric = true;
|
||||
if(count($valueMap) > 0){
|
||||
foreach(Utils::stringifyKeys($valueMap) as $value => $newValue){
|
||||
if(!is_numeric($value)){
|
||||
$allNumeric = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if($allNumeric){
|
||||
//add a dummy key to force the JSON to be an object and not a list
|
||||
$valueMap["dummy"] = "map_not_list";
|
||||
}
|
||||
}
|
||||
|
||||
return new BlockStateUpgradeSchemaFlattenInfo(
|
||||
$idPrefix,
|
||||
$propertyName,
|
||||
$idSuffix,
|
||||
$valueMap,
|
||||
$propertyType,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[][][] $candidateFlattenedValues
|
||||
* @phpstan-param array<string, array<string, array<string, string>>> $candidateFlattenedValues
|
||||
* @param string[] $candidateFlattenPropertyTypes
|
||||
* @phpstan-param array<string, class-string<ByteTag|IntTag|StringTag>> $candidateFlattenPropertyTypes
|
||||
*
|
||||
* @return BlockStateUpgradeSchemaFlattenedName[][]
|
||||
* @phpstan-return array<string, array<string, BlockStateUpgradeSchemaFlattenedName>>
|
||||
* @return BlockStateUpgradeSchemaFlattenInfo[][]
|
||||
* @phpstan-return array<string, array<string, BlockStateUpgradeSchemaFlattenInfo>>
|
||||
*/
|
||||
function buildFlattenPropertyRules(array $candidateFlattenedValues) : array{
|
||||
function buildFlattenPropertyRules(array $candidateFlattenedValues, array $candidateFlattenPropertyTypes) : array{
|
||||
$flattenPropertyRules = [];
|
||||
foreach(Utils::stringifyKeys($candidateFlattenedValues) as $propertyName => $filters){
|
||||
foreach(Utils::stringifyKeys($filters) as $filter => $valueToId){
|
||||
$ids = array_values($valueToId);
|
||||
|
||||
//TODO: this is a bit too enthusiastic. For example, when flattening the old "stone", it will see that
|
||||
//"granite", "andesite", "stone" etc all have "e" as a common suffix, which works, but looks a bit daft.
|
||||
//This also causes more remaps to be generated than necessary, since some of the values are already
|
||||
//contained in the new ID.
|
||||
$idPrefix = findCommonPrefix($ids);
|
||||
$idSuffix = findCommonSuffix($ids);
|
||||
if(strlen($idSuffix) < 2){
|
||||
$idSuffix = "";
|
||||
}
|
||||
|
||||
$valueMap = [];
|
||||
foreach(Utils::stringifyKeys($valueToId) as $value => $newId){
|
||||
$newValue = substr($newId, strlen($idPrefix), $idSuffix !== "" ? -strlen($idSuffix) : null);
|
||||
if($newValue !== $value){
|
||||
$valueMap[$value] = $newValue;
|
||||
}
|
||||
}
|
||||
|
||||
$flattenPropertyRules[$propertyName][$filter] = new BlockStateUpgradeSchemaFlattenedName(
|
||||
$idPrefix,
|
||||
$propertyName,
|
||||
$idSuffix,
|
||||
$valueMap
|
||||
);
|
||||
$flattenPropertyRules[$propertyName][$filter] = buildFlattenPropertyRule($valueToId, $propertyName, $candidateFlattenPropertyTypes[$propertyName]);
|
||||
}
|
||||
}
|
||||
ksort($flattenPropertyRules, SORT_STRING);
|
||||
@ -406,56 +531,54 @@ function processRemappedStates(array $upgradeTable) : array{
|
||||
$notFlattenedProperties = [];
|
||||
|
||||
$candidateFlattenedValues = [];
|
||||
$candidateFlattenedPropertyTypes = [];
|
||||
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;
|
||||
}
|
||||
|
||||
$filter = $pair->old->getStates();
|
||||
foreach($unchangedStatesByNewName[$pair->new->getName()] as $unchangedPropertyName){
|
||||
if($unchangedPropertyName === $propertyName){
|
||||
$notFlattenedProperties[$propertyName] = true;
|
||||
continue 2;
|
||||
}
|
||||
unset($filter[$unchangedPropertyName]);
|
||||
}
|
||||
unset($filter[$propertyName]);
|
||||
|
||||
$rawFilter = encodeOrderedProperties($filter);
|
||||
if(isset($candidateFlattenedValues[$propertyName][$rawFilter])){
|
||||
$valuesToIds = $candidateFlattenedValues[$propertyName][$rawFilter];
|
||||
$existingNewId = $valuesToIds[$rawValue] ?? null;
|
||||
if($existingNewId !== null && $existingNewId !== $pair->new->getName()){
|
||||
//this old value is associated with multiple new IDs - bad candidate for flattening
|
||||
$notFlattenedProperties[$propertyName] = true;
|
||||
continue;
|
||||
}
|
||||
foreach(Utils::stringifyKeys($valuesToIds) as $otherRawValue => $otherNewId){
|
||||
if($otherRawValue === $rawValue){
|
||||
continue;
|
||||
}
|
||||
if($otherNewId === $pair->new->getName()){
|
||||
//this old value maps to the same new ID as another old value - bad candidate for flattening
|
||||
$notFlattenedProperties[$propertyName] = true;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$candidateFlattenedValues[$propertyName][$rawFilter] ??= [];
|
||||
$expectedType = $candidateFlattenedPropertyTypes[$propertyName] ?? null;
|
||||
if(!checkFlattenPropertySuitability($propertyValue, $expectedType, $pair->new->getName(), $candidateFlattenedValues[$propertyName][$rawFilter])){
|
||||
$notFlattenedProperties[$propertyName] = true;
|
||||
continue;
|
||||
}
|
||||
$candidateFlattenedPropertyTypes[$propertyName] = $expectedType;
|
||||
}
|
||||
}
|
||||
foreach(Utils::stringifyKeys($candidateFlattenedValues) as $propertyName => $filters){
|
||||
foreach($filters as $valuesToIds){
|
||||
if(count(array_unique($valuesToIds)) === 1){
|
||||
//this property doesn't influence the new ID
|
||||
$notFlattenedProperties[$propertyName] = true;
|
||||
continue 2;
|
||||
}
|
||||
$candidateFlattenedValues[$propertyName][$rawFilter][$rawValue] = $pair->new->getName();
|
||||
}
|
||||
}
|
||||
foreach(Utils::stringifyKeys($notFlattenedProperties) as $propertyName => $_){
|
||||
unset($candidateFlattenedValues[$propertyName]);
|
||||
}
|
||||
|
||||
$flattenedProperties = buildFlattenPropertyRules($candidateFlattenedValues);
|
||||
$flattenedProperties = buildFlattenPropertyRules($candidateFlattenedValues, $candidateFlattenedPropertyTypes);
|
||||
$flattenProperty = array_key_first($flattenedProperties);
|
||||
//Properties with fewer rules take up less space for the same result
|
||||
foreach(Utils::stringifyKeys($flattenedProperties) as $propertyName => $rules){
|
||||
if(count($rules) < count($flattenedProperties[$flattenProperty])){
|
||||
$flattenProperty = $propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
@ -475,8 +598,8 @@ function processRemappedStates(array $upgradeTable) : array{
|
||||
ksort($cleanedNewState);
|
||||
if($flattenProperty !== null){
|
||||
$flattenedValue = $cleanedOldState[$flattenProperty] ?? null;
|
||||
if(!$flattenedValue instanceof StringTag){
|
||||
throw new AssumptionFailedError("This should always be a TAG_String ($newName $flattenProperty)");
|
||||
if(!$flattenedValue instanceof StringTag && !$flattenedValue instanceof IntTag && !$flattenedValue instanceof ByteTag){
|
||||
throw new AssumptionFailedError("Non-flattenable type of tag ($newName $flattenProperty) but have " . get_debug_type($flattenedValue));
|
||||
}
|
||||
unset($cleanedOldState[$flattenProperty]);
|
||||
}
|
||||
@ -583,10 +706,15 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad
|
||||
throw new \RuntimeException("States with the same ID should be fully consistent");
|
||||
}
|
||||
}else{
|
||||
//block mapped to multiple different new IDs; we can't guess these, so we just do a plain old remap
|
||||
//even if some of the states stay under the same ID, the compression techniques used by this function
|
||||
//implicitly rely on knowing the full set of old states and their new transformations
|
||||
$result->remappedStates[$oldName] = processRemappedStates($blockStateMappings);
|
||||
//try processing this as a regular state group first
|
||||
//if a property was flattened into the ID, the remaining states will normally be consistent
|
||||
//if not we fall back to remap states and state filters
|
||||
if(!processStateGroup($oldName, $blockStateMappings, $result)){
|
||||
//block mapped to multiple different new IDs; we can't guess these, so we just do a plain old remap
|
||||
//even if some of the states stay under the same ID, the compression techniques used by this function
|
||||
//implicitly rely on knowing the full set of old states and their new transformations
|
||||
$result->remappedStates[$oldName] = processRemappedStates($blockStateMappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -594,18 +722,42 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $argv
|
||||
* @param BlockStateMapping[][] $upgradeTable
|
||||
* @phpstan-param array<string, array<string, BlockStateMapping>> $upgradeTable
|
||||
*/
|
||||
function main(array $argv) : int{
|
||||
if(count($argv) !== 3){
|
||||
fwrite(STDERR, "Required arguments: input file path, output file path\n");
|
||||
return 1;
|
||||
function testBlockStateUpgradeSchema(array $upgradeTable, BlockStateUpgradeSchema $schema) : bool{
|
||||
//TODO: HACK!
|
||||
//the upgrader won't apply the schema if it's the same version and there's only one schema with a matching version
|
||||
//ID (for performance reasons), which is a problem for testing isolated schemas
|
||||
//add a dummy schema to bypass this optimization
|
||||
$dummySchema = new BlockStateUpgradeSchema($schema->maxVersionMajor, $schema->maxVersionMinor, $schema->maxVersionPatch, $schema->maxVersionRevision, $schema->getSchemaId() + 1);
|
||||
$upgrader = new BlockStateUpgrader([$schema, $dummySchema]);
|
||||
|
||||
foreach($upgradeTable as $mappingsByOldName){
|
||||
foreach($mappingsByOldName as $mapping){
|
||||
$expectedNewState = $mapping->new;
|
||||
|
||||
$actualNewState = $upgrader->upgrade($mapping->old);
|
||||
|
||||
if(!$expectedNewState->equals($actualNewState)){
|
||||
\GlobalLogger::get()->error("Expected: " . $expectedNewState->toNbt());
|
||||
\GlobalLogger::get()->error("Actual: " . $actualNewState->toNbt());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$input = $argv[1];
|
||||
$output = $argv[2];
|
||||
return true;
|
||||
}
|
||||
|
||||
$table = loadUpgradeTable($input, false);
|
||||
/**
|
||||
* @param string[] $argv
|
||||
*/
|
||||
function cmdGenerate(array $argv) : int{
|
||||
$upgradeTableFile = $argv[2];
|
||||
$schemaFile = $argv[3];
|
||||
|
||||
$table = loadUpgradeTableFromFile($upgradeTableFile, false);
|
||||
|
||||
ksort($table, SORT_STRING);
|
||||
|
||||
@ -614,13 +766,148 @@ function main(array $argv) : int{
|
||||
\GlobalLogger::get()->warning("All states appear to be the same! No schema generated.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(!testBlockStateUpgradeSchema($table, $diff)){
|
||||
\GlobalLogger::get()->error("Generated schema does not produce the results expected by $upgradeTableFile");
|
||||
\GlobalLogger::get()->error("This is probably a bug in the schema generation code. Please report this to the developers.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
file_put_contents(
|
||||
$output,
|
||||
$schemaFile,
|
||||
json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($diff), JSON_PRETTY_PRINT) . "\n"
|
||||
);
|
||||
\GlobalLogger::get()->info("Schema file $output generated successfully.");
|
||||
\GlobalLogger::get()->info("Schema file $schemaFile generated successfully.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $argv
|
||||
*/
|
||||
function cmdTest(array $argv) : int{
|
||||
$upgradeTableFile = $argv[2];
|
||||
$schemaFile = $argv[3];
|
||||
|
||||
$table = loadUpgradeTableFromFile($upgradeTableFile, false);
|
||||
|
||||
ksort($table, SORT_STRING);
|
||||
|
||||
$schema = BlockStateUpgradeSchemaUtils::loadSchemaFromString(Filesystem::fileGetContents($schemaFile), 0);
|
||||
if(!testBlockStateUpgradeSchema($table, $schema)){
|
||||
\GlobalLogger::get()->error("Schema $schemaFile does not produce the results predicted by $upgradeTableFile");
|
||||
return 1;
|
||||
}
|
||||
\GlobalLogger::get()->info("Schema $schemaFile is valid according to $upgradeTableFile");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $argv
|
||||
*/
|
||||
function cmdUpdate(array $argv) : int{
|
||||
[, , $oldSchemaFile, $oldPaletteFile, $newSchemaFile] = $argv;
|
||||
|
||||
$palette = BlockStateDictionary::loadPaletteFromString(Filesystem::fileGetContents($oldPaletteFile));
|
||||
$schema = BlockStateUpgradeSchemaUtils::loadSchemaFromString(Filesystem::fileGetContents($oldSchemaFile), 0);
|
||||
//TODO: HACK!
|
||||
//the upgrader won't apply the schema if it's the same version and there's only one schema with a matching version
|
||||
//ID (for performance reasons), which is a problem for testing isolated schemas
|
||||
//add a dummy schema to bypass this optimization
|
||||
$dummySchema = new BlockStateUpgradeSchema($schema->maxVersionMajor, $schema->maxVersionMinor, $schema->maxVersionPatch, $schema->maxVersionRevision, $schema->getSchemaId() + 1);
|
||||
$upgrader = new BlockStateUpgrader([$schema, $dummySchema]);
|
||||
|
||||
$tags = [];
|
||||
foreach($palette as $stateData){
|
||||
$tags[] = new TreeRoot($stateData->toNbt());
|
||||
$tags[] = new TreeRoot($upgrader->upgrade($stateData)->toNbt());
|
||||
}
|
||||
|
||||
$upgradeTable = buildUpgradeTableFromData($tags, false);
|
||||
$newSchema = generateBlockStateUpgradeSchema($upgradeTable);
|
||||
|
||||
if(!testBlockStateUpgradeSchema($upgradeTable, $newSchema)){
|
||||
\GlobalLogger::get()->error("Updated schema does not produce the expected results!");
|
||||
\GlobalLogger::get()->error("This is probably a bug in the schema generation code. Please report this to the developers.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
file_put_contents(
|
||||
$newSchemaFile,
|
||||
json_encode(BlockStateUpgradeSchemaUtils::toJsonModel($newSchema), JSON_PRETTY_PRINT) . "\n"
|
||||
);
|
||||
\GlobalLogger::get()->info("Schema file $newSchemaFile updated to new format (from $oldSchemaFile) successfully.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $argv
|
||||
*/
|
||||
function cmdUpdateAll(array $argv) : int{
|
||||
$oldPaletteFilenames = [
|
||||
'1.9.0' => '1.09.0',
|
||||
'1.19.50' => '1.19.50.23_beta',
|
||||
'1.19.60' => '1.19.60.26_beta',
|
||||
'1.19.70' => '1.19.70.26_beta',
|
||||
'1.19.80' => '1.19.80.24_beta',
|
||||
];
|
||||
$schemaDir = $argv[2];
|
||||
$paletteArchiveDir = $argv[3];
|
||||
|
||||
$schemaFileNames = scandir($schemaDir);
|
||||
if($schemaFileNames === false){
|
||||
\GlobalLogger::get()->error("Failed to read schema directory $schemaDir");
|
||||
return 1;
|
||||
}
|
||||
foreach($schemaFileNames as $file){
|
||||
$schemaFile = Path::join($schemaDir, $file);
|
||||
if(!file_exists($schemaFile) || is_dir($schemaFile)){
|
||||
continue;
|
||||
}
|
||||
|
||||
if(preg_match('/^\d{4}_(.+?)_to_(.+?).json/', $file, $matches) !== 1){
|
||||
continue;
|
||||
}
|
||||
$oldPaletteFile = Path::join($paletteArchiveDir, ($oldPaletteFilenames[$matches[1]] ?? $matches[1]) . '.nbt');
|
||||
|
||||
//a bit clunky but it avoids having to make yet another function
|
||||
//TODO: perhaps in the future we should write the result to a tmpfile until all schemas are updated,
|
||||
//and then copy the results into place at the end
|
||||
if(cmdUpdate([$argv[0], "update", $schemaFile, $oldPaletteFile, $schemaFile]) !== 0){
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
\GlobalLogger::get()->info("All schemas updated successfully.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $argv
|
||||
*/
|
||||
function main(array $argv) : int{
|
||||
$options = [
|
||||
"generate" => [["palette upgrade table file", "schema output file"], cmdGenerate(...)],
|
||||
"test" => [["palette upgrade table file", "schema output file"], cmdTest(...)],
|
||||
"update" => [["schema input file", "old palette file", "updated schema output file"], cmdUpdate(...)],
|
||||
"update-all" => [["schema folder", "path to BlockPaletteArchive"], cmdUpdateAll(...)]
|
||||
];
|
||||
|
||||
$selected = $argv[1] ?? null;
|
||||
if($selected === null || !isset($options[$selected])){
|
||||
fwrite(STDERR, "Available commands:\n");
|
||||
foreach($options as $command => [$args, $callback]){
|
||||
fwrite(STDERR, " - $command " . implode(" ", array_map(fn(string $a) => "<$a>", $args)) . "\n");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
$callback = $options[$selected][1];
|
||||
if(count($argv) !== count($options[$selected][0]) + 2){
|
||||
fwrite(STDERR, "Usage: {$argv[0]} $selected " . implode(" ", array_map(fn(string $a) => "<$a>", $options[$selected][0])) . "\n");
|
||||
return 1;
|
||||
}
|
||||
return $callback($argv);
|
||||
}
|
||||
|
||||
exit(main($argv));
|
Loading…
x
Reference in New Issue
Block a user