Merge branch 'minor-next' into stable

This commit is contained in:
Dylan K. Taylor 2023-07-04 14:43:23 +01:00
commit 02ffb04b92
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
49 changed files with 1552 additions and 565 deletions

View File

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

View File

@ -22,7 +22,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pmmpthread": "^6.0.1",
"ext-pmmpthread": "^6.0.4",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
@ -34,7 +34,7 @@
"adhocore/json-comment": "~1.2.0",
"fgrosse/phpasn1": "~2.5.0",
"pocketmine/netresearch-jsonmapper": "~v4.2.999",
"pocketmine/bedrock-block-upgrade-schema": "~2.2.0+bedrock-1.20.0",
"pocketmine/bedrock-block-upgrade-schema": "~3.0.0",
"pocketmine/bedrock-data": "~2.3.0+bedrock-1.20.0",
"pocketmine/bedrock-item-upgrade-schema": "~1.3.0+bedrock-1.20.0",
"pocketmine/bedrock-protocol": "~22.0.0+bedrock-1.20.0",

16
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3b102702eabc31fd8b2651db6c259397",
"content-hash": "ff0fa864a7853011bf4620d870c47ac7",
"packages": [
{
"name": "adhocore/json-comment",
@ -198,16 +198,16 @@
},
{
"name": "pocketmine/bedrock-block-upgrade-schema",
"version": "2.2.0",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/pmmp/BedrockBlockUpgradeSchema.git",
"reference": "79bb3ad542ef19e828fdf1fa6adc54f1fa4b3bb5"
"reference": "8b72c47109e174ac7f17c3ac546748f8e49a5fdf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/79bb3ad542ef19e828fdf1fa6adc54f1fa4b3bb5",
"reference": "79bb3ad542ef19e828fdf1fa6adc54f1fa4b3bb5",
"url": "https://api.github.com/repos/pmmp/BedrockBlockUpgradeSchema/zipball/8b72c47109e174ac7f17c3ac546748f8e49a5fdf",
"reference": "8b72c47109e174ac7f17c3ac546748f8e49a5fdf",
"shasum": ""
},
"type": "library",
@ -218,9 +218,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/2.2.0"
"source": "https://github.com/pmmp/BedrockBlockUpgradeSchema/tree/3.0.0"
},
"time": "2023-05-04T21:49:36+00:00"
"time": "2023-07-03T16:35:44+00:00"
},
{
"name": "pocketmine/bedrock-data",
@ -3020,7 +3020,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pmmpthread": "^6.0.1",
"ext-pmmpthread": "^6.0.4",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",

View File

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

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "5.1.4";
public const BASE_VERSION = "5.2.0";
public const IS_DEVELOPMENT_BUILD = true;
public const BUILD_CHANNEL = "stable";

View File

@ -717,8 +717,24 @@ final class BlockTypeIds{
public const FLOWERING_AZALEA_LEAVES = 10687;
public const REINFORCED_DEEPSLATE = 10688;
public const CAVE_VINES = 10689;
public const GLOW_LICHEN = 10690;
public const CHERRY_BUTTON = 10691;
public const CHERRY_DOOR = 10692;
public const CHERRY_FENCE = 10693;
public const CHERRY_FENCE_GATE = 10694;
public const CHERRY_LEAVES = 10695;
public const CHERRY_LOG = 10696;
public const CHERRY_PLANKS = 10697;
public const CHERRY_PRESSURE_PLATE = 10698;
public const CHERRY_SAPLING = 10699;
public const CHERRY_SIGN = 10700;
public const CHERRY_SLAB = 10701;
public const CHERRY_STAIRS = 10702;
public const CHERRY_TRAPDOOR = 10703;
public const CHERRY_WALL_SIGN = 10704;
public const CHERRY_WOOD = 10705;
public const FIRST_UNUSED_BLOCK_ID = 10690;
public const FIRST_UNUSED_BLOCK_ID = 10706;
private static int $nextDynamicId = self::FIRST_UNUSED_BLOCK_ID;

284
src/block/GlowLichen.php Normal file
View File

@ -0,0 +1,284 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\event\block\BlockSpreadEvent;
use pocketmine\item\Fertilizer;
use pocketmine\item\Item;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\player\Player;
use pocketmine\world\BlockTransaction;
use pocketmine\world\World;
use function array_key_first;
use function count;
use function shuffle;
class GlowLichen extends Transparent{
/** @var int[] */
protected array $faces = [];
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->facingFlags($this->faces);
}
/** @return int[] */
public function getFaces() : array{ return $this->faces; }
public function hasFace(int $face) : bool{
return isset($this->faces[$face]);
}
/**
* @param int[] $faces
* @return $this
*/
public function setFaces(array $faces) : self{
$uniqueFaces = [];
foreach($faces as $face){
Facing::validate($face);
$uniqueFaces[$face] = $face;
}
$this->faces = $uniqueFaces;
return $this;
}
/** @return $this */
public function setFace(int $face, bool $value) : self{
Facing::validate($face);
if($value){
$this->faces[$face] = $face;
}else{
unset($this->faces[$face]);
}
return $this;
}
public function getLightLevel() : int{
return 7;
}
public function isSolid() : bool{
return false;
}
/**
* @return AxisAlignedBB[]
*/
protected function recalculateCollisionBoxes() : array{
return [];
}
public function getSupportType(int $facing) : SupportType{
return SupportType::NONE();
}
public function canBeReplaced() : bool{
return true;
}
public function place(BlockTransaction $tx, Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, ?Player $player = null) : bool{
$this->faces = $blockReplace instanceof GlowLichen ? $blockReplace->faces : [];
$availableFaces = $this->getAvailableFaces();
if(count($availableFaces) === 0){
return false;
}
$opposite = Facing::opposite($face);
$placedFace = isset($availableFaces[$opposite]) ? $opposite : array_key_first($availableFaces);
$this->faces[$placedFace] = $placedFace;
return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player);
}
public function onNearbyBlockChange() : void{
$changed = false;
foreach($this->faces as $face){
if(!$this->getSide($face)->getSupportType(Facing::opposite($face))->equals(SupportType::FULL())){
unset($this->faces[$face]);
$changed = true;
}
}
if($changed){
$world = $this->position->getWorld();
if(count($this->faces) === 0){
$world->useBreakOn($this->position);
}else{
$world->setBlock($this->position, $this);
}
}
}
private function getSpreadBlock(Block $replace, int $spreadFace) : ?Block{
if($replace instanceof self && $replace->hasSameTypeId($this)){
if($replace->hasFace($spreadFace)){
return null;
}
$result = $replace;
}elseif($replace->getTypeId() === BlockTypeIds::AIR){
$result = VanillaBlocks::GLOW_LICHEN();
}else{
//TODO: if this is a water block, generate a waterlogged block
return null;
}
return $result->setFace($spreadFace, true);
}
private function spread(World $world, Vector3 $replacePos, int $spreadFace) : bool{
$supportBlock = $world->getBlock($replacePos->getSide($spreadFace));
$supportFace = Facing::opposite($spreadFace);
if(!$supportBlock->getSupportType($supportFace)->equals(SupportType::FULL())){
return false;
}
$replacedBlock = $supportBlock->getSide($supportFace);
$replacementBlock = $this->getSpreadBlock($replacedBlock, Facing::opposite($supportFace));
if($replacementBlock === null){
return false;
}
$ev = new BlockSpreadEvent($replacedBlock, $this, $replacementBlock);
$ev->call();
if(!$ev->isCancelled()){
$world->setBlock($replacedBlock->getPosition(), $ev->getNewState());
return true;
}
return false;
}
/**
* @phpstan-return \Generator<int, int, void, void>
*/
private static function getShuffledSpreadFaces(int $sourceFace) : \Generator{
$skipAxis = Facing::axis($sourceFace);
$faces = Facing::ALL;
shuffle($faces);
foreach($faces as $spreadFace){
if(Facing::axis($spreadFace) !== $skipAxis){
yield $spreadFace;
}
}
}
private function spreadAroundSupport(int $sourceFace) : bool{
$world = $this->position->getWorld();
$supportPos = $this->position->getSide($sourceFace);
foreach(self::getShuffledSpreadFaces($sourceFace) as $spreadFace){
$replacePos = $supportPos->getSide($spreadFace);
if($this->spread($world, $replacePos, Facing::opposite($spreadFace))){
return true;
}
}
return false;
}
private function spreadAdjacentToSupport(int $sourceFace) : bool{
$world = $this->position->getWorld();
foreach(self::getShuffledSpreadFaces($sourceFace) as $spreadFace){
$replacePos = $this->position->getSide($spreadFace);
if($this->spread($world, $replacePos, $sourceFace)){
return true;
}
}
return false;
}
private function spreadWithinSelf(int $sourceFace) : bool{
foreach(self::getShuffledSpreadFaces($sourceFace) as $spreadFace){
if(!$this->hasFace($spreadFace) && $this->spread($this->position->getWorld(), $this->position, $spreadFace)){
return true;
}
}
return false;
}
public function onInteract(Item $item, int $face, Vector3 $clickVector, ?Player $player = null, array &$returnedItems = []) : bool{
if($item instanceof Fertilizer && count($this->faces) > 0){
$shuffledFaces = $this->faces;
shuffle($shuffledFaces);
$spreadMethods = [
$this->spreadAroundSupport(...),
$this->spreadAdjacentToSupport(...),
$this->spreadWithinSelf(...),
];
shuffle($spreadMethods);
foreach($shuffledFaces as $sourceFace){
foreach($spreadMethods as $spreadMethod){
if($spreadMethod($sourceFace)){
$item->pop();
break 2;
}
}
}
return true;
}
return false;
}
public function getDrops(Item $item) : array{
if(($item->getBlockToolType() & BlockToolType::SHEARS) !== 0){
return $this->getDropsForCompatibleTool($item);
}
return [];
}
public function getFlameEncouragement() : int{
return 15;
}
public function getFlammability() : int{
return 100;
}
/**
* @return array<int, int> $faces
*/
private function getAvailableFaces() : array{
$faces = [];
foreach(Facing::ALL as $face){
if(!$this->hasFace($face) && $this->getSide($face)->getSupportType(Facing::opposite($face))->equals(SupportType::FULL())){
$faces[$face] = $face;
}
}
return $faces;
}
}

View File

@ -148,6 +148,7 @@ class Leaves extends Transparent{
LeavesType::SPRUCE() => VanillaBlocks::SPRUCE_SAPLING(),
LeavesType::MANGROVE(), //TODO: mangrove propagule
LeavesType::AZALEA(), LeavesType::FLOWERING_AZALEA() => null, //TODO: azalea
LeavesType::CHERRY() => null, //TODO: cherry
default => throw new AssumptionFailedError("Unreachable")
})?->asItem();
if($sapling !== null){

View File

@ -159,6 +159,20 @@ use function mb_strtolower;
* @method static CaveVines CAVE_VINES()
* @method static Chain CHAIN()
* @method static ChemicalHeat CHEMICAL_HEAT()
* @method static WoodenButton CHERRY_BUTTON()
* @method static WoodenDoor CHERRY_DOOR()
* @method static WoodenFence CHERRY_FENCE()
* @method static FenceGate CHERRY_FENCE_GATE()
* @method static Leaves CHERRY_LEAVES()
* @method static Wood CHERRY_LOG()
* @method static Planks CHERRY_PLANKS()
* @method static WoodenPressurePlate CHERRY_PRESSURE_PLATE()
* @method static FloorSign CHERRY_SIGN()
* @method static WoodenSlab CHERRY_SLAB()
* @method static WoodenStairs CHERRY_STAIRS()
* @method static WoodenTrapdoor CHERRY_TRAPDOOR()
* @method static WallSign CHERRY_WALL_SIGN()
* @method static Wood CHERRY_WOOD()
* @method static Chest CHEST()
* @method static Opaque CHISELED_DEEPSLATE()
* @method static Opaque CHISELED_NETHER_BRICKS()
@ -417,6 +431,7 @@ use function mb_strtolower;
* @method static ItemFrame GLOWING_ITEM_FRAME()
* @method static GlowingObsidian GLOWING_OBSIDIAN()
* @method static Glowstone GLOWSTONE()
* @method static GlowLichen GLOW_LICHEN()
* @method static Opaque GOLD()
* @method static GoldOre GOLD_ORE()
* @method static Opaque GRANITE()
@ -864,6 +879,7 @@ final class VanillaBlocks{
self::register("glass_pane", new GlassPane(new BID(Ids::GLASS_PANE), "Glass Pane", $glassBreakInfo));
self::register("glowing_obsidian", new GlowingObsidian(new BID(Ids::GLOWING_OBSIDIAN), "Glowing Obsidian", new Info(BreakInfo::pickaxe(10.0, ToolTier::DIAMOND(), 50.0))));
self::register("glowstone", new Glowstone(new BID(Ids::GLOWSTONE), "Glowstone", new Info(BreakInfo::pickaxe(0.3))));
self::register("glow_lichen", new GlowLichen(new BID(Ids::GLOW_LICHEN), "Glow Lichen", new Info(BreakInfo::axe(0.2, null, 0.2))));
self::register("gold", new Opaque(new BID(Ids::GOLD), "Gold Block", new Info(BreakInfo::pickaxe(3.0, ToolTier::IRON(), 30.0))));
$grassBreakInfo = BreakInfo::shovel(0.6);

View File

@ -58,6 +58,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_PLANKS,
WoodType::CRIMSON()->id() => Ids::CRIMSON_PLANKS,
WoodType::WARPED()->id() => Ids::WARPED_PLANKS,
WoodType::CHERRY()->id() => Ids::CHERRY_PLANKS,
default => throw new AssumptionFailedError("All tree types should be covered")
});
}
@ -73,6 +74,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_FENCE,
WoodType::CRIMSON()->id() => Ids::CRIMSON_FENCE,
WoodType::WARPED()->id() => Ids::WARPED_FENCE,
WoodType::CHERRY()->id() => Ids::CHERRY_FENCE,
default => throw new AssumptionFailedError("All tree types should be covered")
});
}
@ -88,6 +90,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_SLAB,
WoodType::CRIMSON()->id() => Ids::CRIMSON_SLAB,
WoodType::WARPED()->id() => Ids::WARPED_SLAB,
WoodType::CHERRY()->id() => Ids::CHERRY_SLAB,
default => throw new AssumptionFailedError("All tree types should be covered")
});
}
@ -103,6 +106,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_LOG,
WoodType::CRIMSON()->id() => Ids::CRIMSON_STEM,
WoodType::WARPED()->id() => Ids::WARPED_STEM,
WoodType::CHERRY()->id() => Ids::CHERRY_LOG,
default => throw new AssumptionFailedError("All tree types should be covered")
});
}
@ -118,6 +122,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_WOOD,
WoodType::CRIMSON()->id() => Ids::CRIMSON_HYPHAE,
WoodType::WARPED()->id() => Ids::WARPED_HYPHAE,
WoodType::CHERRY()->id() => Ids::CHERRY_WOOD,
default => throw new AssumptionFailedError("All tree types should be covered")
});
}
@ -133,6 +138,7 @@ final class WoodLikeBlockIdHelper{
LeavesType::MANGROVE()->id() => Ids::MANGROVE_LEAVES,
LeavesType::AZALEA()->id() => Ids::AZALEA_LEAVES,
LeavesType::FLOWERING_AZALEA()->id() => Ids::FLOWERING_AZALEA_LEAVES,
LeavesType::CHERRY()->id() => Ids::CHERRY_LEAVES,
default => throw new AssumptionFailedError("All leaves types should be covered")
});
}
@ -209,7 +215,12 @@ final class WoodLikeBlockIdHelper{
new BID(Ids::WARPED_WALL_SIGN, TileSign::class),
fn() => VanillaItems::WARPED_SIGN()
];
case WoodType::CHERRY()->id():
return [
new BID(Ids::CHERRY_SIGN, TileSign::class),
new BID(Ids::CHERRY_WALL_SIGN, TileSign::class),
fn() => VanillaItems::CHERRY_SIGN()
];
}
throw new AssumptionFailedError("Switch should cover all wood types");
}
@ -225,6 +236,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_TRAPDOOR,
WoodType::CRIMSON()->id() => Ids::CRIMSON_TRAPDOOR,
WoodType::WARPED()->id() => Ids::WARPED_TRAPDOOR,
WoodType::CHERRY()->id() => Ids::CHERRY_TRAPDOOR,
default => throw new AssumptionFailedError("All wood types should be covered")
});
}
@ -240,6 +252,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_BUTTON,
WoodType::CRIMSON()->id() => Ids::CRIMSON_BUTTON,
WoodType::WARPED()->id() => Ids::WARPED_BUTTON,
WoodType::CHERRY()->id() => Ids::CHERRY_BUTTON,
default => throw new AssumptionFailedError("All wood types should be covered")
});
}
@ -255,6 +268,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_PRESSURE_PLATE,
WoodType::CRIMSON()->id() => Ids::CRIMSON_PRESSURE_PLATE,
WoodType::WARPED()->id() => Ids::WARPED_PRESSURE_PLATE,
WoodType::CHERRY()->id() => Ids::CHERRY_PRESSURE_PLATE,
default => throw new AssumptionFailedError("All wood types should be covered")
});
}
@ -270,6 +284,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_DOOR,
WoodType::CRIMSON()->id() => Ids::CRIMSON_DOOR,
WoodType::WARPED()->id() => Ids::WARPED_DOOR,
WoodType::CHERRY()->id() => Ids::CHERRY_DOOR,
default => throw new AssumptionFailedError("All wood types should be covered")
});
}
@ -285,6 +300,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_FENCE_GATE,
WoodType::CRIMSON()->id() => Ids::CRIMSON_FENCE_GATE,
WoodType::WARPED()->id() => Ids::WARPED_FENCE_GATE,
WoodType::CHERRY()->id() => Ids::CHERRY_FENCE_GATE,
default => throw new AssumptionFailedError("All wood types should be covered")
});
}
@ -300,6 +316,7 @@ final class WoodLikeBlockIdHelper{
WoodType::MANGROVE()->id() => Ids::MANGROVE_STAIRS,
WoodType::CRIMSON()->id() => Ids::CRIMSON_STAIRS,
WoodType::WARPED()->id() => Ids::WARPED_STAIRS,
WoodType::CHERRY()->id() => Ids::CHERRY_STAIRS,
default => throw new AssumptionFailedError("All wood types should be covered")
});
}

View File

@ -34,6 +34,7 @@ use pocketmine\utils\EnumTrait;
* @method static LeavesType ACACIA()
* @method static LeavesType AZALEA()
* @method static LeavesType BIRCH()
* @method static LeavesType CHERRY()
* @method static LeavesType DARK_OAK()
* @method static LeavesType FLOWERING_AZALEA()
* @method static LeavesType JUNGLE()
@ -57,7 +58,8 @@ final class LeavesType{
new self("dark_oak", "Dark Oak"),
new self("mangrove", "Mangrove"),
new self("azalea", "Azalea"),
new self("flowering_azalea", "Flowering Azalea")
new self("flowering_azalea", "Flowering Azalea"),
new self("cherry", "Cherry")
);
}

View File

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

View File

@ -33,6 +33,7 @@ use pocketmine\utils\EnumTrait;
*
* @method static WoodType ACACIA()
* @method static WoodType BIRCH()
* @method static WoodType CHERRY()
* @method static WoodType CRIMSON()
* @method static WoodType DARK_OAK()
* @method static WoodType JUNGLE()
@ -56,7 +57,8 @@ final class WoodType{
new self("dark_oak", "Dark Oak", true),
new self("mangrove", "Mangrove", true),
new self("crimson", "Crimson", false, "Stem", "Hyphae"),
new self("warped", "Warped", false, "Stem", "Hyphae")
new self("warped", "Warped", false, "Stem", "Hyphae"),
new self("cherry", "Cherry", true),
);
}

View File

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

View File

@ -38,6 +38,13 @@ final class BlockLegacyMetadata{
public const CORAL_VARIANT_FIRE = 3;
public const CORAL_VARIANT_HORN = 4;
public const MULTI_FACE_DIRECTION_FLAG_DOWN = 0x01;
public const MULTI_FACE_DIRECTION_FLAG_UP = 0x02;
public const MULTI_FACE_DIRECTION_FLAG_SOUTH = 0x04;
public const MULTI_FACE_DIRECTION_FLAG_WEST = 0x08;
public const MULTI_FACE_DIRECTION_FLAG_NORTH = 0x10;
public const MULTI_FACE_DIRECTION_FLAG_EAST = 0x20;
public const MUSHROOM_BLOCK_ALL_PORES = 0;
public const MUSHROOM_BLOCK_CAP_NORTHWEST_CORNER = 1;
public const MUSHROOM_BLOCK_CAP_NORTH_SIDE = 2;

View File

@ -111,7 +111,10 @@ final class BlockStateData{
return new self($name, $states->getValue(), $version);
}
public function toNbt() : CompoundTag{
/**
* Encodes the blockstate as a TAG_Compound, exactly as it would be in vanilla Bedrock.
*/
public function toVanillaNbt() : CompoundTag{
$statesTag = CompoundTag::create();
foreach(Utils::stringifyKeys($this->states) as $key => $value){
$statesTag->setTag($key, $value);
@ -119,7 +122,15 @@ final class BlockStateData{
return CompoundTag::create()
->setString(self::TAG_NAME, $this->name)
->setInt(self::TAG_VERSION, $this->version)
->setTag(self::TAG_STATES, $statesTag)
->setTag(self::TAG_STATES, $statesTag);
}
/**
* Encodes the blockstate as a TAG_Compound, but with extra PM-specific metadata, used for fixing bugs in old saved
* data. This should be used for anything saved to disk.
*/
public function toNbt() : CompoundTag{
return $this->toVanillaNbt()
->setLong(VersionInfo::TAG_WORLD_DATA_VERSION, VersionInfo::WORLD_DATA_VERSION);
}

View File

@ -79,6 +79,7 @@ use pocketmine\block\Froglight;
use pocketmine\block\FrostedIce;
use pocketmine\block\Furnace;
use pocketmine\block\GlazedTerracotta;
use pocketmine\block\GlowLichen;
use pocketmine\block\HayBale;
use pocketmine\block\Hopper;
use pocketmine\block\ItemFrame;
@ -192,7 +193,9 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->registerFlatColorBlockSerializers();
$this->registerFlatCoralSerializers();
$this->registerCauldronSerializers();
$this->registerWoodBlockSerializers();
$this->registerFlatWoodBlockSerializers();
$this->registerLegacyWoodBlockSerializers();
$this->registerLeavesSerializers();
$this->registerSimpleSerializers();
$this->registerSerializers();
}
@ -390,20 +393,206 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::WATER_CAULDRON(), fn(FillableCauldron $b) => Helper::encodeCauldron(StringValues::CAULDRON_LIQUID_WATER, $b->getFillLevel()));
}
private function registerWoodBlockSerializers() : void{
$this->mapSimple(Blocks::ACACIA_FENCE(), Ids::ACACIA_FENCE);
$this->mapSimple(Blocks::BIRCH_FENCE(), Ids::BIRCH_FENCE);
$this->mapSimple(Blocks::DARK_OAK_FENCE(), Ids::DARK_OAK_FENCE);
$this->mapSimple(Blocks::JUNGLE_FENCE(), Ids::JUNGLE_FENCE);
$this->mapSimple(Blocks::OAK_FENCE(), Ids::OAK_FENCE);
$this->mapSimple(Blocks::SPRUCE_FENCE(), Ids::SPRUCE_FENCE);
private function registerFlatWoodBlockSerializers() : void{
$this->map(Blocks::ACACIA_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::ACACIA_BUTTON)));
$this->map(Blocks::ACACIA_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::ACACIA_DOOR)));
$this->map(Blocks::ACACIA_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::ACACIA_FENCE_GATE)));
$this->map(Blocks::ACACIA_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::ACACIA_PRESSURE_PLATE)));
$this->map(Blocks::ACACIA_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::ACACIA_STANDING_SIGN)));
$this->map(Blocks::ACACIA_STAIRS(), fn(WoodenStairs $block) => Helper::encodeStairs($block, new Writer(Ids::ACACIA_STAIRS)));
$this->map(Blocks::ACACIA_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::ACACIA_TRAPDOOR)));
$this->map(Blocks::ACACIA_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::ACACIA_WALL_SIGN)));
$this->mapLog(Blocks::ACACIA_LOG(), Ids::ACACIA_LOG, Ids::STRIPPED_ACACIA_LOG);
$this->mapSimple(Blocks::ACACIA_FENCE(), Ids::ACACIA_FENCE);
//wood, planks and slabs still use the old way of storing wood type
$this->map(Blocks::BIRCH_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::BIRCH_BUTTON)));
$this->map(Blocks::BIRCH_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::BIRCH_DOOR)));
$this->map(Blocks::BIRCH_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::BIRCH_FENCE_GATE)));
$this->map(Blocks::BIRCH_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::BIRCH_PRESSURE_PLATE)));
$this->map(Blocks::BIRCH_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::BIRCH_STANDING_SIGN)));
$this->map(Blocks::BIRCH_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::BIRCH_TRAPDOOR)));
$this->map(Blocks::BIRCH_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::BIRCH_WALL_SIGN)));
$this->mapLog(Blocks::BIRCH_LOG(), Ids::BIRCH_LOG, Ids::STRIPPED_BIRCH_LOG);
$this->mapSimple(Blocks::BIRCH_FENCE(), Ids::BIRCH_FENCE);
$this->mapStairs(Blocks::BIRCH_STAIRS(), Ids::BIRCH_STAIRS);
//wood, planks and slabs still use the old way of storing wood type
$this->map(Blocks::CHERRY_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::CHERRY_BUTTON)));
$this->map(Blocks::CHERRY_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::CHERRY_DOOR)));
$this->map(Blocks::CHERRY_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::CHERRY_FENCE_GATE)));
$this->map(Blocks::CHERRY_LOG(), fn(Wood $block) => Helper::encodeLog($block, Ids::CHERRY_LOG, Ids::STRIPPED_CHERRY_LOG));
$this->map(Blocks::CHERRY_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::CHERRY_PRESSURE_PLATE)));
$this->map(Blocks::CHERRY_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::CHERRY_STANDING_SIGN)));
$this->map(Blocks::CHERRY_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::CHERRY_TRAPDOOR)));
$this->map(Blocks::CHERRY_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::CHERRY_WALL_SIGN)));
$this->mapSimple(Blocks::CHERRY_FENCE(), Ids::CHERRY_FENCE);
$this->mapSimple(Blocks::CHERRY_PLANKS(), Ids::CHERRY_PLANKS);
$this->mapSlab(Blocks::CHERRY_SLAB(), Ids::CHERRY_SLAB, Ids::CHERRY_DOUBLE_SLAB);
$this->mapStairs(Blocks::CHERRY_STAIRS(), Ids::CHERRY_STAIRS);
$this->map(Blocks::CHERRY_WOOD(), function(Wood $block) : Writer{
//we can't use the standard method for this because cherry_wood has a useless property attached to it
if(!$block->isStripped()){
return Writer::create(Ids::CHERRY_WOOD)
->writePillarAxis($block->getAxis())
->writeBool(StateNames::STRIPPED_BIT, false); //this is useless, but it has to be written
}else{
return Writer::create(Ids::STRIPPED_CHERRY_WOOD)
->writePillarAxis($block->getAxis());
}
});
$this->map(Blocks::CRIMSON_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::CRIMSON_BUTTON)));
$this->map(Blocks::CRIMSON_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::CRIMSON_DOOR)));
$this->map(Blocks::CRIMSON_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::CRIMSON_FENCE_GATE)));
$this->map(Blocks::CRIMSON_HYPHAE(), fn(Wood $block) => Helper::encodeLog($block, Ids::CRIMSON_HYPHAE, Ids::STRIPPED_CRIMSON_HYPHAE));
$this->map(Blocks::CRIMSON_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::CRIMSON_PRESSURE_PLATE)));
$this->map(Blocks::CRIMSON_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::CRIMSON_STANDING_SIGN)));
$this->map(Blocks::CRIMSON_STEM(), fn(Wood $block) => Helper::encodeLog($block, Ids::CRIMSON_STEM, Ids::STRIPPED_CRIMSON_STEM));
$this->map(Blocks::CRIMSON_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::CRIMSON_TRAPDOOR)));
$this->map(Blocks::CRIMSON_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::CRIMSON_WALL_SIGN)));
$this->mapSimple(Blocks::CRIMSON_FENCE(), Ids::CRIMSON_FENCE);
$this->mapSimple(Blocks::CRIMSON_PLANKS(), Ids::CRIMSON_PLANKS);
$this->mapSlab(Blocks::CRIMSON_SLAB(), Ids::CRIMSON_SLAB, Ids::CRIMSON_DOUBLE_SLAB);
$this->mapStairs(Blocks::CRIMSON_STAIRS(), Ids::CRIMSON_STAIRS);
$this->map(Blocks::DARK_OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::DARK_OAK_BUTTON)));
$this->map(Blocks::DARK_OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::DARK_OAK_DOOR)));
$this->map(Blocks::DARK_OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::DARK_OAK_FENCE_GATE)));
$this->map(Blocks::DARK_OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::DARK_OAK_PRESSURE_PLATE)));
$this->map(Blocks::DARK_OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::DARKOAK_STANDING_SIGN)));
$this->map(Blocks::DARK_OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::DARK_OAK_TRAPDOOR)));
$this->map(Blocks::DARK_OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::DARKOAK_WALL_SIGN)));
$this->mapLog(Blocks::DARK_OAK_LOG(), Ids::DARK_OAK_LOG, Ids::STRIPPED_DARK_OAK_LOG);
$this->mapSimple(Blocks::DARK_OAK_FENCE(), Ids::DARK_OAK_FENCE);
$this->mapStairs(Blocks::DARK_OAK_STAIRS(), Ids::DARK_OAK_STAIRS);
//wood, planks and slabs still use the old way of storing wood type
$this->map(Blocks::JUNGLE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::JUNGLE_BUTTON)));
$this->map(Blocks::JUNGLE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::JUNGLE_DOOR)));
$this->map(Blocks::JUNGLE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::JUNGLE_FENCE_GATE)));
$this->map(Blocks::JUNGLE_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::JUNGLE_PRESSURE_PLATE)));
$this->map(Blocks::JUNGLE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::JUNGLE_STANDING_SIGN)));
$this->map(Blocks::JUNGLE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::JUNGLE_TRAPDOOR)));
$this->map(Blocks::JUNGLE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::JUNGLE_WALL_SIGN)));
$this->mapLog(Blocks::JUNGLE_LOG(), Ids::JUNGLE_LOG, Ids::STRIPPED_JUNGLE_LOG);
$this->mapSimple(Blocks::JUNGLE_FENCE(), Ids::JUNGLE_FENCE);
$this->mapStairs(Blocks::JUNGLE_STAIRS(), Ids::JUNGLE_STAIRS);
//wood, planks and slabs still use the old way of storing wood type
$this->map(Blocks::MANGROVE_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::MANGROVE_BUTTON)));
$this->map(Blocks::MANGROVE_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::MANGROVE_DOOR)));
$this->map(Blocks::MANGROVE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::MANGROVE_FENCE_GATE)));
$this->map(Blocks::MANGROVE_LOG(), fn(Wood $block) => Helper::encodeLog($block, Ids::MANGROVE_LOG, Ids::STRIPPED_MANGROVE_LOG));
$this->map(Blocks::MANGROVE_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::MANGROVE_PRESSURE_PLATE)));
$this->map(Blocks::MANGROVE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::MANGROVE_STANDING_SIGN)));
$this->map(Blocks::MANGROVE_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::MANGROVE_TRAPDOOR)));
$this->map(Blocks::MANGROVE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::MANGROVE_WALL_SIGN)));
$this->mapSimple(Blocks::MANGROVE_FENCE(), Ids::MANGROVE_FENCE);
$this->mapSimple(Blocks::MANGROVE_PLANKS(), Ids::MANGROVE_PLANKS);
$this->mapSlab(Blocks::MANGROVE_SLAB(), Ids::MANGROVE_SLAB, Ids::MANGROVE_DOUBLE_SLAB);
$this->mapStairs(Blocks::MANGROVE_STAIRS(), Ids::MANGROVE_STAIRS);
$this->map(Blocks::MANGROVE_WOOD(), function(Wood $block) : Writer{
//we can't use the standard method for this because mangrove_wood has a useless property attached to it
if(!$block->isStripped()){
return Writer::create(Ids::MANGROVE_WOOD)
->writePillarAxis($block->getAxis())
->writeBool(StateNames::STRIPPED_BIT, false); //this is useless, but it has to be written
}else{
return Writer::create(Ids::STRIPPED_MANGROVE_WOOD)
->writePillarAxis($block->getAxis());
}
});
$this->map(Blocks::OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::WOODEN_BUTTON)));
$this->map(Blocks::OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::WOODEN_DOOR)));
$this->map(Blocks::OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::FENCE_GATE)));
$this->map(Blocks::OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::WOODEN_PRESSURE_PLATE)));
$this->map(Blocks::OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::STANDING_SIGN)));
$this->map(Blocks::OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::TRAPDOOR)));
$this->map(Blocks::OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WALL_SIGN)));
$this->mapLog(Blocks::OAK_LOG(), Ids::OAK_LOG, Ids::STRIPPED_OAK_LOG);
$this->mapSimple(Blocks::OAK_FENCE(), Ids::OAK_FENCE);
$this->mapStairs(Blocks::OAK_STAIRS(), Ids::OAK_STAIRS);
//wood, planks and slabs still use the old way of storing wood type
$this->mapSimple(Blocks::SPRUCE_FENCE(), Ids::SPRUCE_FENCE);
$this->mapLog(Blocks::SPRUCE_LOG(), Ids::SPRUCE_LOG, Ids::STRIPPED_SPRUCE_LOG);
$this->map(Blocks::SPRUCE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::SPRUCE_BUTTON)));
$this->map(Blocks::SPRUCE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::SPRUCE_DOOR)));
$this->map(Blocks::SPRUCE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::SPRUCE_FENCE_GATE)));
$this->map(Blocks::SPRUCE_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::SPRUCE_PRESSURE_PLATE)));
$this->map(Blocks::SPRUCE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::SPRUCE_STANDING_SIGN)));
$this->mapStairs(Blocks::SPRUCE_STAIRS(), Ids::SPRUCE_STAIRS);
$this->map(Blocks::SPRUCE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::SPRUCE_TRAPDOOR)));
$this->map(Blocks::SPRUCE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::SPRUCE_WALL_SIGN)));
//wood, planks and slabs still use the old way of storing wood type
$this->map(Blocks::WARPED_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::WARPED_BUTTON)));
$this->map(Blocks::WARPED_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::WARPED_DOOR)));
$this->map(Blocks::WARPED_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::WARPED_FENCE_GATE)));
$this->map(Blocks::WARPED_HYPHAE(), fn(Wood $block) => Helper::encodeLog($block, Ids::WARPED_HYPHAE, Ids::STRIPPED_WARPED_HYPHAE));
$this->map(Blocks::WARPED_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::WARPED_PRESSURE_PLATE)));
$this->map(Blocks::WARPED_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::WARPED_STANDING_SIGN)));
$this->map(Blocks::WARPED_STEM(), fn(Wood $block) => Helper::encodeLog($block, Ids::WARPED_STEM, Ids::STRIPPED_WARPED_STEM));
$this->map(Blocks::WARPED_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::WARPED_TRAPDOOR)));
$this->map(Blocks::WARPED_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WARPED_WALL_SIGN)));
$this->mapSimple(Blocks::WARPED_FENCE(), Ids::WARPED_FENCE);
$this->mapSimple(Blocks::WARPED_PLANKS(), Ids::WARPED_PLANKS);
$this->mapSlab(Blocks::WARPED_SLAB(), Ids::WARPED_SLAB, Ids::WARPED_DOUBLE_SLAB);
$this->mapStairs(Blocks::WARPED_STAIRS(), Ids::WARPED_STAIRS);
}
private function registerLegacyWoodBlockSerializers() : void{
foreach([
StringValues::WOOD_TYPE_ACACIA => Blocks::ACACIA_PLANKS(),
StringValues::WOOD_TYPE_BIRCH => Blocks::BIRCH_PLANKS(),
StringValues::WOOD_TYPE_DARK_OAK => Blocks::DARK_OAK_PLANKS(),
StringValues::WOOD_TYPE_JUNGLE => Blocks::JUNGLE_PLANKS(),
StringValues::WOOD_TYPE_OAK => Blocks::OAK_PLANKS(),
StringValues::WOOD_TYPE_SPRUCE => Blocks::SPRUCE_PLANKS(),
] as $woodType => $block){
$this->map($block, fn() => Writer::create(Ids::PLANKS)
->writeString(StateNames::WOOD_TYPE, $woodType));
}
foreach([
StringValues::WOOD_TYPE_ACACIA => Blocks::ACACIA_SLAB(),
StringValues::WOOD_TYPE_BIRCH => Blocks::BIRCH_SLAB(),
StringValues::WOOD_TYPE_DARK_OAK => Blocks::DARK_OAK_SLAB(),
StringValues::WOOD_TYPE_JUNGLE => Blocks::JUNGLE_SLAB(),
StringValues::WOOD_TYPE_OAK => Blocks::OAK_SLAB(),
StringValues::WOOD_TYPE_SPRUCE => Blocks::SPRUCE_SLAB(),
] as $woodType => $block){
$this->map($block, fn(Slab $block) => Helper::encodeWoodenSlab($block, $woodType));
}
foreach([
Blocks::ACACIA_WOOD(),
Blocks::BIRCH_WOOD(),
Blocks::DARK_OAK_WOOD(),
Blocks::JUNGLE_WOOD(),
Blocks::OAK_WOOD(),
Blocks::SPRUCE_WOOD(),
] as $block){
$this->map($block, fn(Wood $block) => Helper::encodeAllSidedLog($block));
}
}
private function registerLeavesSerializers() : void{
//flattened IDs
$this->map(Blocks::AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES)));
$this->map(Blocks::CHERRY_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::CHERRY_LEAVES)));
$this->map(Blocks::FLOWERING_AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES_FLOWERED)));
$this->map(Blocks::MANGROVE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::MANGROVE_LEAVES)));
//legacy mess
$this->map(Blocks::ACACIA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves2($block, StringValues::NEW_LEAF_TYPE_ACACIA));
$this->map(Blocks::BIRCH_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_BIRCH));
$this->map(Blocks::DARK_OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves2($block, StringValues::NEW_LEAF_TYPE_DARK_OAK));
$this->map(Blocks::JUNGLE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_JUNGLE));
$this->map(Blocks::OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_OAK));
$this->map(Blocks::SPRUCE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_SPRUCE));
}
private function registerSimpleSerializers() : void{
@ -436,8 +625,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->mapSimple(Blocks::CRACKED_NETHER_BRICKS(), Ids::CRACKED_NETHER_BRICKS);
$this->mapSimple(Blocks::CRACKED_POLISHED_BLACKSTONE_BRICKS(), Ids::CRACKED_POLISHED_BLACKSTONE_BRICKS);
$this->mapSimple(Blocks::CRAFTING_TABLE(), Ids::CRAFTING_TABLE);
$this->mapSimple(Blocks::CRIMSON_FENCE(), Ids::CRIMSON_FENCE);
$this->mapSimple(Blocks::CRIMSON_PLANKS(), Ids::CRIMSON_PLANKS);
$this->mapSimple(Blocks::CRYING_OBSIDIAN(), Ids::CRYING_OBSIDIAN);
$this->mapSimple(Blocks::DANDELION(), Ids::YELLOW_FLOWER);
$this->mapSimple(Blocks::DEAD_BUSH(), Ids::DEADBUSH);
@ -607,8 +794,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->mapSimple(Blocks::LEGACY_STONECUTTER(), Ids::STONECUTTER);
$this->mapSimple(Blocks::LILY_PAD(), Ids::WATERLILY);
$this->mapSimple(Blocks::MAGMA(), Ids::MAGMA);
$this->mapSimple(Blocks::MANGROVE_FENCE(), Ids::MANGROVE_FENCE);
$this->mapSimple(Blocks::MANGROVE_PLANKS(), Ids::MANGROVE_PLANKS);
$this->mapSimple(Blocks::MANGROVE_ROOTS(), Ids::MANGROVE_ROOTS);
$this->mapSimple(Blocks::MELON(), Ids::MELON_BLOCK);
$this->mapSimple(Blocks::MONSTER_SPAWNER(), Ids::MOB_SPAWNER);
@ -655,27 +840,12 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->mapSimple(Blocks::SPORE_BLOSSOM(), Ids::SPORE_BLOSSOM);
$this->mapSimple(Blocks::TINTED_GLASS(), Ids::TINTED_GLASS);
$this->mapSimple(Blocks::TUFF(), Ids::TUFF);
$this->mapSimple(Blocks::WARPED_FENCE(), Ids::WARPED_FENCE);
$this->mapSimple(Blocks::WARPED_PLANKS(), Ids::WARPED_PLANKS);
$this->mapSimple(Blocks::WARPED_WART_BLOCK(), Ids::WARPED_WART_BLOCK);
$this->mapSimple(Blocks::WITHER_ROSE(), Ids::WITHER_ROSE);
}
private function registerSerializers() : void{
$this->map(Blocks::ACACIA_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::ACACIA_BUTTON)));
$this->map(Blocks::ACACIA_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::ACACIA_DOOR)));
$this->map(Blocks::ACACIA_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::ACACIA_FENCE_GATE)));
$this->map(Blocks::ACACIA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves2($block, StringValues::NEW_LEAF_TYPE_ACACIA));
$this->map(Blocks::ACACIA_PLANKS(), fn() => Writer::create(Ids::PLANKS)
->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_ACACIA));
$this->map(Blocks::ACACIA_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::ACACIA_PRESSURE_PLATE)));
$this->map(Blocks::ACACIA_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_ACACIA));
$this->map(Blocks::ACACIA_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::ACACIA_STANDING_SIGN)));
$this->map(Blocks::ACACIA_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_ACACIA));
$this->map(Blocks::ACACIA_STAIRS(), fn(WoodenStairs $block) => Helper::encodeStairs($block, new Writer(Ids::ACACIA_STAIRS)));
$this->map(Blocks::ACACIA_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::ACACIA_TRAPDOOR)));
$this->map(Blocks::ACACIA_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::ACACIA_WALL_SIGN)));
$this->map(Blocks::ACACIA_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
$this->map(Blocks::ACTIVATOR_RAIL(), function(ActivatorRail $block) : Writer{
return Writer::create(Ids::ACTIVATOR_RAIL)
->writeBool(StateNames::RAIL_DATA_BIT, $block->isPowered())
@ -698,7 +868,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
default => throw new BlockStateSerializeException("Invalid Anvil damage {$damage}"),
});
});
$this->map(Blocks::AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES)));
$this->map(Blocks::AZURE_BLUET(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_HOUSTONIA));
$this->map(Blocks::BAMBOO(), function(Bamboo $block) : Writer{
return Writer::create(Ids::BAMBOO)
@ -749,20 +918,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
->writeLegacyHorizontalFacing($block->getFacing());
});
$this->map(Blocks::BIRCH_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::BIRCH_BUTTON)));
$this->map(Blocks::BIRCH_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::BIRCH_DOOR)));
$this->map(Blocks::BIRCH_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::BIRCH_FENCE_GATE)));
$this->map(Blocks::BIRCH_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_BIRCH));
$this->map(Blocks::BIRCH_PLANKS(), fn() => Writer::create(Ids::PLANKS)
->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_BIRCH));
$this->map(Blocks::BIRCH_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::BIRCH_PRESSURE_PLATE)));
$this->map(Blocks::BIRCH_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_BIRCH));
$this->map(Blocks::BIRCH_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::BIRCH_STANDING_SIGN)));
$this->map(Blocks::BIRCH_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_BIRCH));
$this->mapStairs(Blocks::BIRCH_STAIRS(), Ids::BIRCH_STAIRS);
$this->map(Blocks::BIRCH_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::BIRCH_TRAPDOOR)));
$this->map(Blocks::BIRCH_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::BIRCH_WALL_SIGN)));
$this->map(Blocks::BIRCH_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
$this->mapSlab(Blocks::BLACKSTONE_SLAB(), Ids::BLACKSTONE_SLAB, Ids::BLACKSTONE_DOUBLE_SLAB);
$this->mapStairs(Blocks::BLACKSTONE_STAIRS(), Ids::BLACKSTONE_STAIRS);
$this->map(Blocks::BLACKSTONE_WALL(), fn(Wall $block) => Helper::encodeWall($block, new Writer(Ids::BLACKSTONE_WALL)));
@ -934,35 +1090,11 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
});
$this->map(Blocks::CORNFLOWER(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_CORNFLOWER));
$this->map(Blocks::CRACKED_STONE_BRICKS(), fn() => Helper::encodeStoneBricks(StringValues::STONE_BRICK_TYPE_CRACKED));
$this->map(Blocks::CRIMSON_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::CRIMSON_BUTTON)));
$this->map(Blocks::CRIMSON_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::CRIMSON_DOOR)));
$this->map(Blocks::CRIMSON_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::CRIMSON_FENCE_GATE)));
$this->map(Blocks::CRIMSON_HYPHAE(), fn(Wood $block) => Helper::encodeLog($block, Ids::CRIMSON_HYPHAE, Ids::STRIPPED_CRIMSON_HYPHAE));
$this->map(Blocks::CRIMSON_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::CRIMSON_PRESSURE_PLATE)));
$this->map(Blocks::CRIMSON_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::CRIMSON_STANDING_SIGN)));
$this->mapSlab(Blocks::CRIMSON_SLAB(), Ids::CRIMSON_SLAB, Ids::CRIMSON_DOUBLE_SLAB);
$this->mapStairs(Blocks::CRIMSON_STAIRS(), Ids::CRIMSON_STAIRS);
$this->map(Blocks::CRIMSON_STEM(), fn(Wood $block) => Helper::encodeLog($block, Ids::CRIMSON_STEM, Ids::STRIPPED_CRIMSON_STEM));
$this->map(Blocks::CRIMSON_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::CRIMSON_TRAPDOOR)));
$this->map(Blocks::CRIMSON_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::CRIMSON_WALL_SIGN)));
$this->map(Blocks::CUT_RED_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::RED_SANDSTONE, StringValues::SAND_STONE_TYPE_CUT));
$this->map(Blocks::CUT_RED_SANDSTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab4($block, StringValues::STONE_SLAB_TYPE_4_CUT_RED_SANDSTONE));
$this->map(Blocks::CUT_SANDSTONE(), fn() => Helper::encodeSandstone(Ids::SANDSTONE, StringValues::SAND_STONE_TYPE_CUT));
$this->map(Blocks::CUT_SANDSTONE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab4($block, StringValues::STONE_SLAB_TYPE_4_CUT_SANDSTONE));
$this->map(Blocks::DARK_OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::DARK_OAK_BUTTON)));
$this->map(Blocks::DARK_OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::DARK_OAK_DOOR)));
$this->map(Blocks::DARK_OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::DARK_OAK_FENCE_GATE)));
$this->map(Blocks::DARK_OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves2($block, StringValues::NEW_LEAF_TYPE_DARK_OAK));
$this->map(Blocks::DARK_OAK_PLANKS(), fn() => Writer::create(Ids::PLANKS)
->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_DARK_OAK));
$this->map(Blocks::DARK_OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::DARK_OAK_PRESSURE_PLATE)));
$this->map(Blocks::DARK_OAK_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_DARK_OAK));
$this->map(Blocks::DARK_OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::DARKOAK_STANDING_SIGN)));
$this->map(Blocks::DARK_OAK_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_DARK_OAK));
$this->mapStairs(Blocks::DARK_OAK_STAIRS(), Ids::DARK_OAK_STAIRS);
$this->map(Blocks::DARK_OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::DARK_OAK_TRAPDOOR)));
$this->map(Blocks::DARK_OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::DARKOAK_WALL_SIGN)));
$this->map(Blocks::DARK_OAK_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
$this->map(Blocks::DARK_PRISMARINE(), fn() => Writer::create(Ids::PRISMARINE)
->writeString(StateNames::PRISMARINE_BLOCK_TYPE, StringValues::PRISMARINE_BLOCK_TYPE_DARK));
$this->map(Blocks::DARK_PRISMARINE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab2($block, StringValues::STONE_SLAB_TYPE_2_PRISMARINE_DARK));
@ -1040,7 +1172,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
return Writer::create(Ids::FLOWER_POT)
->writeBool(StateNames::UPDATE_BIT, false); //to keep MCPE happy
});
$this->map(Blocks::FLOWERING_AZALEA_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::AZALEA_LEAVES_FLOWERED)));
$this->map(Blocks::FROGLIGHT(), function(Froglight $block){
return Writer::create(match($block->getFroglightType()){
FroglightType::OCHRE() => Ids::OCHRE_FROGLIGHT,
@ -1055,6 +1186,10 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
->writeInt(StateNames::AGE, $block->getAge());
});
$this->map(Blocks::FURNACE(), fn(Furnace $block) => Helper::encodeFurnace($block, Ids::FURNACE, Ids::LIT_FURNACE));
$this->map(Blocks::GLOW_LICHEN(), function(GlowLichen $block) : Writer{
return Writer::create(Ids::GLOW_LICHEN)
->writeFacingFlags($block->getFaces());
});
$this->map(Blocks::GLOWING_ITEM_FRAME(), fn(ItemFrame $block) => Helper::encodeItemFrame($block, Ids::GLOW_FRAME));
$this->map(Blocks::GRANITE(), fn() => Helper::encodeStone(StringValues::STONE_TYPE_GRANITE));
$this->map(Blocks::GRANITE_SLAB(), fn(Slab $block) => Helper::encodeStoneSlab3($block, StringValues::STONE_SLAB_TYPE_3_GRANITE));
@ -1086,20 +1221,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
$this->map(Blocks::IRON_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::IRON_DOOR)));
$this->map(Blocks::IRON_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::IRON_TRAPDOOR)));
$this->map(Blocks::ITEM_FRAME(), fn(ItemFrame $block) => Helper::encodeItemFrame($block, Ids::FRAME));
$this->map(Blocks::JUNGLE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::JUNGLE_BUTTON)));
$this->map(Blocks::JUNGLE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::JUNGLE_DOOR)));
$this->map(Blocks::JUNGLE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::JUNGLE_FENCE_GATE)));
$this->map(Blocks::JUNGLE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_JUNGLE));
$this->map(Blocks::JUNGLE_PLANKS(), fn() => Writer::create(Ids::PLANKS)
->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_JUNGLE));
$this->map(Blocks::JUNGLE_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::JUNGLE_PRESSURE_PLATE)));
$this->map(Blocks::JUNGLE_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_JUNGLE));
$this->map(Blocks::JUNGLE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::JUNGLE_STANDING_SIGN)));
$this->map(Blocks::JUNGLE_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_JUNGLE));
$this->mapStairs(Blocks::JUNGLE_STAIRS(), Ids::JUNGLE_STAIRS);
$this->map(Blocks::JUNGLE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::JUNGLE_TRAPDOOR)));
$this->map(Blocks::JUNGLE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::JUNGLE_WALL_SIGN)));
$this->map(Blocks::JUNGLE_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
$this->map(Blocks::LAB_TABLE(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, StringValues::CHEMISTRY_TABLE_TYPE_LAB_TABLE, new Writer(Ids::CHEMISTRY_TABLE)));
$this->map(Blocks::LADDER(), function(Ladder $block) : Writer{
return Writer::create(Ids::LADDER)
@ -1149,28 +1271,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
return Writer::create(Ids::LOOM)
->writeLegacyHorizontalFacing($block->getFacing());
});
$this->map(Blocks::MANGROVE_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::MANGROVE_BUTTON)));
$this->map(Blocks::MANGROVE_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::MANGROVE_DOOR)));
$this->map(Blocks::MANGROVE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::MANGROVE_FENCE_GATE)));
$this->map(Blocks::MANGROVE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves($block, new Writer(Ids::MANGROVE_LEAVES)));
$this->map(Blocks::MANGROVE_LOG(), fn(Wood $block) => Helper::encodeLog($block, Ids::MANGROVE_LOG, Ids::STRIPPED_MANGROVE_LOG));
$this->map(Blocks::MANGROVE_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::MANGROVE_PRESSURE_PLATE)));
$this->map(Blocks::MANGROVE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::MANGROVE_STANDING_SIGN)));
$this->mapSlab(Blocks::MANGROVE_SLAB(), Ids::MANGROVE_SLAB, Ids::MANGROVE_DOUBLE_SLAB);
$this->mapStairs(Blocks::MANGROVE_STAIRS(), Ids::MANGROVE_STAIRS);
$this->map(Blocks::MANGROVE_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::MANGROVE_TRAPDOOR)));
$this->map(Blocks::MANGROVE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::MANGROVE_WALL_SIGN)));
$this->map(Blocks::MANGROVE_WOOD(), function(Wood $block) : Writer{
//we can't use the standard method for this because mangrove_wood has a useless property attached to it
if(!$block->isStripped()){
return Writer::create(Ids::MANGROVE_WOOD)
->writePillarAxis($block->getAxis())
->writeBool(StateNames::STRIPPED_BIT, false); //this is useless, but it has to be written
}else{
return Writer::create(Ids::STRIPPED_MANGROVE_WOOD)
->writePillarAxis($block->getAxis());
}
});
$this->map(Blocks::MATERIAL_REDUCER(), fn(ChemistryTable $block) => Helper::encodeChemistryTable($block, StringValues::CHEMISTRY_TABLE_TYPE_MATERIAL_REDUCER, new Writer(Ids::CHEMISTRY_TABLE)));
$this->map(Blocks::MELON_STEM(), fn(MelonStem $block) => Helper::encodeStem($block, new Writer(Ids::MELON_STEM)));
$this->map(Blocks::MOB_HEAD(), function(MobHead $block) : Writer{
@ -1206,20 +1306,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
return Writer::create(Ids::NETHER_WART)
->writeInt(StateNames::AGE, $block->getAge());
});
$this->map(Blocks::OAK_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::WOODEN_BUTTON)));
$this->map(Blocks::OAK_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::WOODEN_DOOR)));
$this->map(Blocks::OAK_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::FENCE_GATE)));
$this->map(Blocks::OAK_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_OAK));
$this->map(Blocks::OAK_PLANKS(), fn() => Writer::create(Ids::PLANKS)
->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_OAK));
$this->map(Blocks::OAK_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::WOODEN_PRESSURE_PLATE)));
$this->map(Blocks::OAK_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_OAK));
$this->map(Blocks::OAK_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::STANDING_SIGN)));
$this->map(Blocks::OAK_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_OAK));
$this->mapStairs(Blocks::OAK_STAIRS(), Ids::OAK_STAIRS);
$this->map(Blocks::OAK_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::TRAPDOOR)));
$this->map(Blocks::OAK_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WALL_SIGN)));
$this->map(Blocks::OAK_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
$this->map(Blocks::ORANGE_TULIP(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_TULIP_ORANGE));
$this->map(Blocks::OXEYE_DAISY(), fn() => Helper::encodeRedFlower(StringValues::FLOWER_TYPE_OXEYE));
$this->map(Blocks::PEONY(), fn(DoublePlant $block) => Helper::encodeDoublePlant($block, StringValues::DOUBLE_PLANT_TYPE_PAEONIA, Writer::create(Ids::DOUBLE_PLANT)));
@ -1367,20 +1454,7 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
return Writer::create(Ids::SPONGE)
->writeString(StateNames::SPONGE_TYPE, $block->isWet() ? StringValues::SPONGE_TYPE_WET : StringValues::SPONGE_TYPE_DRY);
});
$this->map(Blocks::SPRUCE_BUTTON(), fn(WoodenButton $block) => Helper::encodeButton($block, new Writer(Ids::SPRUCE_BUTTON)));
$this->map(Blocks::SPRUCE_DOOR(), fn(WoodenDoor $block) => Helper::encodeDoor($block, new Writer(Ids::SPRUCE_DOOR)));
$this->map(Blocks::SPRUCE_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::SPRUCE_FENCE_GATE)));
$this->map(Blocks::SPRUCE_LEAVES(), fn(Leaves $block) => Helper::encodeLeaves1($block, StringValues::OLD_LEAF_TYPE_SPRUCE));
$this->map(Blocks::SPRUCE_PLANKS(), fn() => Writer::create(Ids::PLANKS)
->writeString(StateNames::WOOD_TYPE, StringValues::WOOD_TYPE_SPRUCE));
$this->map(Blocks::SPRUCE_PRESSURE_PLATE(), fn(WoodenPressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::SPRUCE_PRESSURE_PLATE)));
$this->map(Blocks::SPRUCE_SAPLING(), fn(Sapling $block) => Helper::encodeSapling($block, StringValues::SAPLING_TYPE_SPRUCE));
$this->map(Blocks::SPRUCE_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::SPRUCE_STANDING_SIGN)));
$this->map(Blocks::SPRUCE_SLAB(), fn(Slab $block) => Helper::encodeWoodenSlab($block, StringValues::WOOD_TYPE_SPRUCE));
$this->mapStairs(Blocks::SPRUCE_STAIRS(), Ids::SPRUCE_STAIRS);
$this->map(Blocks::SPRUCE_TRAPDOOR(), fn(WoodenTrapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::SPRUCE_TRAPDOOR)));
$this->map(Blocks::SPRUCE_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::SPRUCE_WALL_SIGN)));
$this->map(Blocks::SPRUCE_WOOD(), fn(Wood $block) => Helper::encodeAllSidedLog($block));
$this->map(Blocks::STAINED_CLAY(), function(StainedHardenedClay $block) : Writer{
return Writer::create(Ids::STAINED_HARDENED_CLAY)
->writeColor($block->getColor());
@ -1477,17 +1551,6 @@ final class BlockObjectToStateSerializer implements BlockStateSerializer{
->writeBool(StateNames::DEAD_BIT, $block->isDead())
->writeCoralFacing($block->getFacing());
});
$this->map(Blocks::WARPED_BUTTON(), fn(Button $block) => Helper::encodeButton($block, new Writer(Ids::WARPED_BUTTON)));
$this->map(Blocks::WARPED_DOOR(), fn(Door $block) => Helper::encodeDoor($block, new Writer(Ids::WARPED_DOOR)));
$this->map(Blocks::WARPED_FENCE_GATE(), fn(FenceGate $block) => Helper::encodeFenceGate($block, new Writer(Ids::WARPED_FENCE_GATE)));
$this->map(Blocks::WARPED_HYPHAE(), fn(Wood $block) => Helper::encodeLog($block, Ids::WARPED_HYPHAE, Ids::STRIPPED_WARPED_HYPHAE));
$this->map(Blocks::WARPED_PRESSURE_PLATE(), fn(SimplePressurePlate $block) => Helper::encodeSimplePressurePlate($block, new Writer(Ids::WARPED_PRESSURE_PLATE)));
$this->map(Blocks::WARPED_SIGN(), fn(FloorSign $block) => Helper::encodeFloorSign($block, new Writer(Ids::WARPED_STANDING_SIGN)));
$this->mapSlab(Blocks::WARPED_SLAB(), Ids::WARPED_SLAB, Ids::WARPED_DOUBLE_SLAB);
$this->mapStairs(Blocks::WARPED_STAIRS(), Ids::WARPED_STAIRS);
$this->map(Blocks::WARPED_STEM(), fn(Wood $block) => Helper::encodeLog($block, Ids::WARPED_STEM, Ids::STRIPPED_WARPED_STEM));
$this->map(Blocks::WARPED_TRAPDOOR(), fn(Trapdoor $block) => Helper::encodeTrapdoor($block, new Writer(Ids::WARPED_TRAPDOOR)));
$this->map(Blocks::WARPED_WALL_SIGN(), fn(WallSign $block) => Helper::encodeWallSign($block, new Writer(Ids::WARPED_WALL_SIGN)));
$this->map(Blocks::WATER(), fn(Water $block) => Helper::encodeLiquid($block, Ids::WATER, Ids::FLOWING_WATER));
$this->map(Blocks::WEEPING_VINES(), function(NetherVines $block) : Writer{
return Writer::create(Ids::WEEPING_VINES)

View File

@ -28,6 +28,7 @@ use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\SlabType;
use pocketmine\block\utils\WallConnectionType;
use pocketmine\data\bedrock\block\BlockLegacyMetadata;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\data\bedrock\block\BlockStateDeserializeException;
use pocketmine\data\bedrock\block\BlockStateNames;
@ -134,6 +135,29 @@ final class BlockStateReader{
]);
}
/**
* @return int[]
* @phpstan-return array<int, int>
*/
public function readFacingFlags() : array{
$result = [];
$flags = $this->readBoundedInt(BlockStateNames::MULTI_FACE_DIRECTION_BITS, 0, 63);
foreach([
BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_DOWN => Facing::DOWN,
BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_UP => Facing::UP,
BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_NORTH => Facing::NORTH,
BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_SOUTH => Facing::SOUTH,
BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_WEST => Facing::WEST,
BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_EAST => Facing::EAST
] as $flag => $facing){
if(($flags & $flag) !== 0){
$result[$facing] = $facing;
}
}
return $result;
}
/** @throws BlockStateDeserializeException */
public function readEndRodFacingDirection() : int{
$result = $this->readFacingDirection();

View File

@ -75,7 +75,9 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->registerFlatColorBlockDeserializers();
$this->registerFlatCoralDeserializers();
$this->registerCauldronDeserializers();
$this->registerWoodBlockDeserializers();
$this->registerFlatWoodBlockDeserializers();
$this->registerLegacyWoodBlockDeserializers();
$this->registerLeavesDeserializers();
$this->registerSimpleDeserializers();
$this->registerDeserializers();
}
@ -283,20 +285,191 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->map(Ids::CAULDRON, $deserializer);
}
private function registerWoodBlockDeserializers() : void{
$this->mapSimple(Ids::ACACIA_FENCE, fn() => Blocks::ACACIA_FENCE());
$this->mapSimple(Ids::BIRCH_FENCE, fn() => Blocks::BIRCH_FENCE());
$this->mapSimple(Ids::DARK_OAK_FENCE, fn() => Blocks::DARK_OAK_FENCE());
$this->mapSimple(Ids::JUNGLE_FENCE, fn() => Blocks::JUNGLE_FENCE());
$this->mapSimple(Ids::OAK_FENCE, fn() => Blocks::OAK_FENCE());
$this->mapSimple(Ids::SPRUCE_FENCE, fn() => Blocks::SPRUCE_FENCE());
private function registerFlatWoodBlockDeserializers() : void{
$this->map(Ids::ACACIA_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::ACACIA_BUTTON(), $in));
$this->map(Ids::ACACIA_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::ACACIA_DOOR(), $in));
$this->map(Ids::ACACIA_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::ACACIA_FENCE_GATE(), $in));
$this->map(Ids::ACACIA_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::ACACIA_PRESSURE_PLATE(), $in));
$this->map(Ids::ACACIA_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::ACACIA_SIGN(), $in));
$this->map(Ids::ACACIA_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::ACACIA_TRAPDOOR(), $in));
$this->map(Ids::ACACIA_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::ACACIA_WALL_SIGN(), $in));
$this->mapLog(Ids::ACACIA_LOG, Ids::STRIPPED_ACACIA_LOG, fn() => Blocks::ACACIA_LOG());
$this->mapSimple(Ids::ACACIA_FENCE, fn() => Blocks::ACACIA_FENCE());
$this->mapStairs(Ids::ACACIA_STAIRS, fn() => Blocks::ACACIA_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::BIRCH_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::BIRCH_BUTTON(), $in));
$this->map(Ids::BIRCH_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::BIRCH_DOOR(), $in));
$this->map(Ids::BIRCH_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::BIRCH_FENCE_GATE(), $in));
$this->map(Ids::BIRCH_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::BIRCH_PRESSURE_PLATE(), $in));
$this->map(Ids::BIRCH_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::BIRCH_SIGN(), $in));
$this->map(Ids::BIRCH_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::BIRCH_TRAPDOOR(), $in));
$this->map(Ids::BIRCH_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::BIRCH_WALL_SIGN(), $in));
$this->mapLog(Ids::BIRCH_LOG, Ids::STRIPPED_BIRCH_LOG, fn() => Blocks::BIRCH_LOG());
$this->mapSimple(Ids::BIRCH_FENCE, fn() => Blocks::BIRCH_FENCE());
$this->mapStairs(Ids::BIRCH_STAIRS, fn() => Blocks::BIRCH_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::CHERRY_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::CHERRY_BUTTON(), $in));
$this->map(Ids::CHERRY_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::CHERRY_DOOR(), $in));
$this->map(Ids::CHERRY_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::CHERRY_FENCE_GATE(), $in));
$this->map(Ids::CHERRY_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::CHERRY_PRESSURE_PLATE(), $in));
$this->map(Ids::CHERRY_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::CHERRY_SIGN(), $in));
$this->map(Ids::CHERRY_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::CHERRY_TRAPDOOR(), $in));
$this->map(Ids::CHERRY_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::CHERRY_WALL_SIGN(), $in));
$this->mapLog(Ids::CHERRY_LOG, Ids::STRIPPED_CHERRY_LOG, fn() => Blocks::CHERRY_LOG());
$this->mapSimple(Ids::CHERRY_FENCE, fn() => Blocks::CHERRY_FENCE());
$this->mapSimple(Ids::CHERRY_PLANKS, fn() => Blocks::CHERRY_PLANKS());
$this->mapSlab(Ids::CHERRY_SLAB, Ids::CHERRY_DOUBLE_SLAB, fn() => Blocks::CHERRY_SLAB());
$this->mapStairs(Ids::CHERRY_STAIRS, fn() => Blocks::CHERRY_STAIRS());
$this->map(Ids::CHERRY_WOOD, function(Reader $in){
$in->ignored(StateNames::STRIPPED_BIT); //this is also ignored by vanilla
return Helper::decodeLog(Blocks::CHERRY_WOOD(), false, $in);
});
$this->map(Ids::STRIPPED_CHERRY_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::CHERRY_WOOD(), true, $in));
$this->map(Ids::CRIMSON_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::CRIMSON_BUTTON(), $in));
$this->map(Ids::CRIMSON_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::CRIMSON_DOOR(), $in));
$this->map(Ids::CRIMSON_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::CRIMSON_FENCE_GATE(), $in));
$this->map(Ids::CRIMSON_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::CRIMSON_PRESSURE_PLATE(), $in));
$this->map(Ids::CRIMSON_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::CRIMSON_SIGN(), $in));
$this->map(Ids::CRIMSON_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::CRIMSON_TRAPDOOR(), $in));
$this->map(Ids::CRIMSON_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::CRIMSON_WALL_SIGN(), $in));
$this->mapLog(Ids::CRIMSON_HYPHAE, Ids::STRIPPED_CRIMSON_HYPHAE, fn() => Blocks::CRIMSON_HYPHAE());
$this->mapLog(Ids::CRIMSON_STEM, Ids::STRIPPED_CRIMSON_STEM, fn() => Blocks::CRIMSON_STEM());
$this->mapSimple(Ids::CRIMSON_FENCE, fn() => Blocks::CRIMSON_FENCE());
$this->mapSimple(Ids::CRIMSON_PLANKS, fn() => Blocks::CRIMSON_PLANKS());
$this->mapSlab(Ids::CRIMSON_SLAB, Ids::CRIMSON_DOUBLE_SLAB, fn() => Blocks::CRIMSON_SLAB());
$this->mapStairs(Ids::CRIMSON_STAIRS, fn() => Blocks::CRIMSON_STAIRS());
$this->map(Ids::DARKOAK_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::DARK_OAK_SIGN(), $in));
$this->map(Ids::DARKOAK_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::DARK_OAK_WALL_SIGN(), $in));
$this->map(Ids::DARK_OAK_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::DARK_OAK_BUTTON(), $in));
$this->map(Ids::DARK_OAK_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::DARK_OAK_DOOR(), $in));
$this->map(Ids::DARK_OAK_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::DARK_OAK_FENCE_GATE(), $in));
$this->map(Ids::DARK_OAK_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::DARK_OAK_PRESSURE_PLATE(), $in));
$this->map(Ids::DARK_OAK_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::DARK_OAK_TRAPDOOR(), $in));
$this->mapLog(Ids::DARK_OAK_LOG, Ids::STRIPPED_DARK_OAK_LOG, fn() => Blocks::DARK_OAK_LOG());
$this->mapSimple(Ids::DARK_OAK_FENCE, fn() => Blocks::DARK_OAK_FENCE());
$this->mapStairs(Ids::DARK_OAK_STAIRS, fn() => Blocks::DARK_OAK_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::JUNGLE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::JUNGLE_BUTTON(), $in));
$this->map(Ids::JUNGLE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::JUNGLE_DOOR(), $in));
$this->map(Ids::JUNGLE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::JUNGLE_FENCE_GATE(), $in));
$this->map(Ids::JUNGLE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::JUNGLE_PRESSURE_PLATE(), $in));
$this->map(Ids::JUNGLE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::JUNGLE_SIGN(), $in));
$this->map(Ids::JUNGLE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::JUNGLE_TRAPDOOR(), $in));
$this->map(Ids::JUNGLE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::JUNGLE_WALL_SIGN(), $in));
$this->mapLog(Ids::JUNGLE_LOG, Ids::STRIPPED_JUNGLE_LOG, fn() => Blocks::JUNGLE_LOG());
$this->mapSimple(Ids::JUNGLE_FENCE, fn() => Blocks::JUNGLE_FENCE());
$this->mapStairs(Ids::JUNGLE_STAIRS, fn() => Blocks::JUNGLE_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::MANGROVE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::MANGROVE_BUTTON(), $in));
$this->map(Ids::MANGROVE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::MANGROVE_DOOR(), $in));
$this->map(Ids::MANGROVE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::MANGROVE_FENCE_GATE(), $in));
$this->map(Ids::MANGROVE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::MANGROVE_PRESSURE_PLATE(), $in));
$this->map(Ids::MANGROVE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::MANGROVE_SIGN(), $in));
$this->map(Ids::MANGROVE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::MANGROVE_TRAPDOOR(), $in));
$this->map(Ids::MANGROVE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::MANGROVE_WALL_SIGN(), $in));
$this->mapLog(Ids::MANGROVE_LOG, Ids::STRIPPED_MANGROVE_LOG, fn() => Blocks::MANGROVE_LOG());
$this->mapSimple(Ids::MANGROVE_FENCE, fn() => Blocks::MANGROVE_FENCE());
$this->mapSimple(Ids::MANGROVE_PLANKS, fn() => Blocks::MANGROVE_PLANKS());
$this->mapSlab(Ids::MANGROVE_SLAB, Ids::MANGROVE_DOUBLE_SLAB, fn() => Blocks::MANGROVE_SLAB());
$this->mapStairs(Ids::MANGROVE_STAIRS, fn() => Blocks::MANGROVE_STAIRS());
$this->map(Ids::MANGROVE_WOOD, function(Reader $in){
$in->ignored(StateNames::STRIPPED_BIT); //this is also ignored by vanilla
return Helper::decodeLog(Blocks::MANGROVE_WOOD(), false, $in);
});
$this->map(Ids::STRIPPED_MANGROVE_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_WOOD(), true, $in));
//oak - due to age, many of these don't specify "oak", making for confusing reading
$this->map(Ids::WOODEN_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::OAK_BUTTON(), $in));
$this->map(Ids::WOODEN_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::OAK_DOOR(), $in));
$this->map(Ids::FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::OAK_FENCE_GATE(), $in));
$this->map(Ids::WOODEN_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::OAK_PRESSURE_PLATE(), $in));
$this->map(Ids::STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::OAK_SIGN(), $in));
$this->map(Ids::TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::OAK_TRAPDOOR(), $in));
$this->map(Ids::WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::OAK_WALL_SIGN(), $in));
$this->mapLog(Ids::OAK_LOG, Ids::STRIPPED_OAK_LOG, fn() => Blocks::OAK_LOG());
$this->mapSimple(Ids::OAK_FENCE, fn() => Blocks::OAK_FENCE());
$this->mapStairs(Ids::OAK_STAIRS, fn() => Blocks::OAK_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::SPRUCE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::SPRUCE_BUTTON(), $in));
$this->map(Ids::SPRUCE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::SPRUCE_DOOR(), $in));
$this->map(Ids::SPRUCE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::SPRUCE_FENCE_GATE(), $in));
$this->map(Ids::SPRUCE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::SPRUCE_PRESSURE_PLATE(), $in));
$this->map(Ids::SPRUCE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::SPRUCE_SIGN(), $in));
$this->map(Ids::SPRUCE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::SPRUCE_TRAPDOOR(), $in));
$this->map(Ids::SPRUCE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::SPRUCE_WALL_SIGN(), $in));
$this->mapLog(Ids::SPRUCE_LOG, Ids::STRIPPED_SPRUCE_LOG, fn() => Blocks::SPRUCE_LOG());
$this->mapSimple(Ids::SPRUCE_FENCE, fn() => Blocks::SPRUCE_FENCE());
$this->mapStairs(Ids::SPRUCE_STAIRS, fn() => Blocks::SPRUCE_STAIRS());
//wood, planks and slabs still use the old way of storing wood type
$this->map(Ids::WARPED_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::WARPED_BUTTON(), $in));
$this->map(Ids::WARPED_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::WARPED_DOOR(), $in));
$this->map(Ids::WARPED_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::WARPED_FENCE_GATE(), $in));
$this->map(Ids::WARPED_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::WARPED_PRESSURE_PLATE(), $in));
$this->map(Ids::WARPED_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::WARPED_SIGN(), $in));
$this->map(Ids::WARPED_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::WARPED_TRAPDOOR(), $in));
$this->map(Ids::WARPED_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::WARPED_WALL_SIGN(), $in));
$this->mapLog(Ids::WARPED_HYPHAE, Ids::STRIPPED_WARPED_HYPHAE, fn() => Blocks::WARPED_HYPHAE());
$this->mapLog(Ids::WARPED_STEM, Ids::STRIPPED_WARPED_STEM, fn() => Blocks::WARPED_STEM());
$this->mapSimple(Ids::WARPED_FENCE, fn() => Blocks::WARPED_FENCE());
$this->mapSimple(Ids::WARPED_PLANKS, fn() => Blocks::WARPED_PLANKS());
$this->mapSlab(Ids::WARPED_SLAB, Ids::WARPED_DOUBLE_SLAB, fn() => Blocks::WARPED_SLAB());
$this->mapStairs(Ids::WARPED_STAIRS, fn() => Blocks::WARPED_STAIRS());
}
private function registerLegacyWoodBlockDeserializers() : void{
$this->map(Ids::PLANKS, function(Reader $in) : Block{
return match($woodName = $in->readString(StateNames::WOOD_TYPE)){
StringValues::WOOD_TYPE_OAK => Blocks::OAK_PLANKS(),
StringValues::WOOD_TYPE_SPRUCE => Blocks::SPRUCE_PLANKS(),
StringValues::WOOD_TYPE_BIRCH => Blocks::BIRCH_PLANKS(),
StringValues::WOOD_TYPE_JUNGLE => Blocks::JUNGLE_PLANKS(),
StringValues::WOOD_TYPE_ACACIA => Blocks::ACACIA_PLANKS(),
StringValues::WOOD_TYPE_DARK_OAK => Blocks::DARK_OAK_PLANKS(),
default => throw $in->badValueException(StateNames::WOOD_TYPE, $woodName),
};
});
$this->mapSlab(Ids::WOODEN_SLAB, Ids::DOUBLE_WOODEN_SLAB, fn(Reader $in) => Helper::mapWoodenSlabType($in));
$this->map(Ids::WOOD, fn(Reader $in) : Block => Helper::decodeLog(match($woodType = $in->readString(StateNames::WOOD_TYPE)){
StringValues::WOOD_TYPE_ACACIA => Blocks::ACACIA_WOOD(),
StringValues::WOOD_TYPE_BIRCH => Blocks::BIRCH_WOOD(),
StringValues::WOOD_TYPE_DARK_OAK => Blocks::DARK_OAK_WOOD(),
StringValues::WOOD_TYPE_JUNGLE => Blocks::JUNGLE_WOOD(),
StringValues::WOOD_TYPE_OAK => Blocks::OAK_WOOD(),
StringValues::WOOD_TYPE_SPRUCE => Blocks::SPRUCE_WOOD(),
default => throw $in->badValueException(StateNames::WOOD_TYPE, $woodType),
}, $in->readBool(StateNames::STRIPPED_BIT), $in));
}
private function registerLeavesDeserializers() : void{
//flattened IDs
$this->map(Ids::AZALEA_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::AZALEA_LEAVES(), $in));
$this->map(Ids::AZALEA_LEAVES_FLOWERED, fn(Reader $in) => Helper::decodeLeaves(Blocks::FLOWERING_AZALEA_LEAVES(), $in));
$this->map(Ids::CHERRY_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::CHERRY_LEAVES(), $in));
$this->map(Ids::MANGROVE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::MANGROVE_LEAVES(), $in));
//legacy mess
$this->map(Ids::LEAVES, fn(Reader $in) => Helper::decodeLeaves(match($type = $in->readString(StateNames::OLD_LEAF_TYPE)){
StringValues::OLD_LEAF_TYPE_BIRCH => Blocks::BIRCH_LEAVES(),
StringValues::OLD_LEAF_TYPE_JUNGLE => Blocks::JUNGLE_LEAVES(),
StringValues::OLD_LEAF_TYPE_OAK => Blocks::OAK_LEAVES(),
StringValues::OLD_LEAF_TYPE_SPRUCE => Blocks::SPRUCE_LEAVES(),
default => throw $in->badValueException(StateNames::OLD_LEAF_TYPE, $type),
}, $in));
$this->map(Ids::LEAVES2, fn(Reader $in) => Helper::decodeLeaves(match($type = $in->readString(StateNames::NEW_LEAF_TYPE)){
StringValues::NEW_LEAF_TYPE_ACACIA => Blocks::ACACIA_LEAVES(),
StringValues::NEW_LEAF_TYPE_DARK_OAK => Blocks::DARK_OAK_LEAVES(),
default => throw $in->badValueException(StateNames::NEW_LEAF_TYPE, $type),
}, $in));
}
private function registerSimpleDeserializers() : void{
@ -328,8 +501,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->mapSimple(Ids::CRACKED_NETHER_BRICKS, fn() => Blocks::CRACKED_NETHER_BRICKS());
$this->mapSimple(Ids::CRACKED_POLISHED_BLACKSTONE_BRICKS, fn() => Blocks::CRACKED_POLISHED_BLACKSTONE_BRICKS());
$this->mapSimple(Ids::CRAFTING_TABLE, fn() => Blocks::CRAFTING_TABLE());
$this->mapSimple(Ids::CRIMSON_FENCE, fn() => Blocks::CRIMSON_FENCE());
$this->mapSimple(Ids::CRIMSON_PLANKS, fn() => Blocks::CRIMSON_PLANKS());
$this->mapSimple(Ids::CRYING_OBSIDIAN, fn() => Blocks::CRYING_OBSIDIAN());
$this->mapSimple(Ids::DEADBUSH, fn() => Blocks::DEAD_BUSH());
$this->mapSimple(Ids::DEEPSLATE_BRICKS, fn() => Blocks::DEEPSLATE_BRICKS());
@ -496,8 +667,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->mapSimple(Ids::LAPIS_BLOCK, fn() => Blocks::LAPIS_LAZULI());
$this->mapSimple(Ids::LAPIS_ORE, fn() => Blocks::LAPIS_LAZULI_ORE());
$this->mapSimple(Ids::MAGMA, fn() => Blocks::MAGMA());
$this->mapSimple(Ids::MANGROVE_FENCE, fn() => Blocks::MANGROVE_FENCE());
$this->mapSimple(Ids::MANGROVE_PLANKS, fn() => Blocks::MANGROVE_PLANKS());
$this->mapSimple(Ids::MANGROVE_ROOTS, fn() => Blocks::MANGROVE_ROOTS());
$this->mapSimple(Ids::MELON_BLOCK, fn() => Blocks::MELON());
$this->mapSimple(Ids::MOB_SPAWNER, fn() => Blocks::MONSTER_SPAWNER());
@ -545,8 +714,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
$this->mapSimple(Ids::TINTED_GLASS, fn() => Blocks::TINTED_GLASS());
$this->mapSimple(Ids::TUFF, fn() => Blocks::TUFF());
$this->mapSimple(Ids::UNDYED_SHULKER_BOX, fn() => Blocks::SHULKER_BOX());
$this->mapSimple(Ids::WARPED_FENCE, fn() => Blocks::WARPED_FENCE());
$this->mapSimple(Ids::WARPED_PLANKS, fn() => Blocks::WARPED_PLANKS());
$this->mapSimple(Ids::WARPED_WART_BLOCK, fn() => Blocks::WARPED_WART_BLOCK());
$this->mapSimple(Ids::WATERLILY, fn() => Blocks::LILY_PAD());
$this->mapSimple(Ids::WEB, fn() => Blocks::COBWEB());
@ -555,14 +722,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
}
private function registerDeserializers() : void{
$this->map(Ids::ACACIA_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::ACACIA_BUTTON(), $in));
$this->map(Ids::ACACIA_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::ACACIA_DOOR(), $in));
$this->map(Ids::ACACIA_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::ACACIA_FENCE_GATE(), $in));
$this->map(Ids::ACACIA_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::ACACIA_PRESSURE_PLATE(), $in));
$this->mapStairs(Ids::ACACIA_STAIRS, fn() => Blocks::ACACIA_STAIRS());
$this->map(Ids::ACACIA_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::ACACIA_SIGN(), $in));
$this->map(Ids::ACACIA_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::ACACIA_TRAPDOOR(), $in));
$this->map(Ids::ACACIA_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::ACACIA_WALL_SIGN(), $in));
$this->map(Ids::ACTIVATOR_RAIL, function(Reader $in) : Block{
return Blocks::ACTIVATOR_RAIL()
->setPowered($in->readBool(StateNames::RAIL_DATA_BIT))
@ -580,8 +739,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
})
->setFacing($in->readLegacyHorizontalFacing());
});
$this->map(Ids::AZALEA_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::AZALEA_LEAVES(), $in));
$this->map(Ids::AZALEA_LEAVES_FLOWERED, fn(Reader $in) => Helper::decodeLeaves(Blocks::FLOWERING_AZALEA_LEAVES(), $in));
$this->map(Ids::BAMBOO, function(Reader $in) : Block{
return Blocks::BAMBOO()
->setLeafSize(match($value = $in->readString(StateNames::BAMBOO_LEAF_SIZE)){
@ -627,14 +784,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
->setFacing($in->readLegacyHorizontalFacing())
->setAttachmentType($in->readBellAttachmentType());
});
$this->map(Ids::BIRCH_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::BIRCH_BUTTON(), $in));
$this->map(Ids::BIRCH_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::BIRCH_DOOR(), $in));
$this->map(Ids::BIRCH_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::BIRCH_FENCE_GATE(), $in));
$this->map(Ids::BIRCH_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::BIRCH_PRESSURE_PLATE(), $in));
$this->mapStairs(Ids::BIRCH_STAIRS, fn() => Blocks::BIRCH_STAIRS());
$this->map(Ids::BIRCH_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::BIRCH_SIGN(), $in));
$this->map(Ids::BIRCH_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::BIRCH_TRAPDOOR(), $in));
$this->map(Ids::BIRCH_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::BIRCH_WALL_SIGN(), $in));
$this->mapSlab(Ids::BLACKSTONE_SLAB, Ids::BLACKSTONE_DOUBLE_SLAB, fn() => Blocks::BLACKSTONE_SLAB());
$this->mapStairs(Ids::BLACKSTONE_STAIRS, fn() => Blocks::BLACKSTONE_STAIRS());
$this->map(Ids::BLACKSTONE_WALL, fn(Reader $in) => Helper::decodeWall(Blocks::BLACKSTONE_WALL(), $in));
@ -756,26 +905,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
return Helper::decodeWallCoralFan(Blocks::WALL_CORAL_FAN(), $in)
->setCoralType(CoralType::HORN());
});
$this->map(Ids::CRIMSON_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::CRIMSON_BUTTON(), $in));
$this->map(Ids::CRIMSON_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::CRIMSON_DOOR(), $in));
$this->mapSlab(Ids::CRIMSON_SLAB, Ids::CRIMSON_DOUBLE_SLAB, fn() => Blocks::CRIMSON_SLAB());
$this->map(Ids::CRIMSON_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::CRIMSON_FENCE_GATE(), $in));
$this->map(Ids::CRIMSON_HYPHAE, fn(Reader $in) => Helper::decodeLog(Blocks::CRIMSON_HYPHAE(), false, $in));
$this->map(Ids::CRIMSON_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::CRIMSON_PRESSURE_PLATE(), $in));
$this->mapStairs(Ids::CRIMSON_STAIRS, fn() => Blocks::CRIMSON_STAIRS());
$this->map(Ids::CRIMSON_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::CRIMSON_SIGN(), $in));
$this->map(Ids::CRIMSON_STEM, fn(Reader $in) => Helper::decodeLog(Blocks::CRIMSON_STEM(), false, $in));
$this->map(Ids::CRIMSON_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::CRIMSON_TRAPDOOR(), $in));
$this->map(Ids::CRIMSON_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::CRIMSON_WALL_SIGN(), $in));
$this->map(Ids::DARK_OAK_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::DARK_OAK_BUTTON(), $in));
$this->map(Ids::DARK_OAK_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::DARK_OAK_DOOR(), $in));
$this->map(Ids::DARK_OAK_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::DARK_OAK_FENCE_GATE(), $in));
$this->map(Ids::DARK_OAK_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::DARK_OAK_PRESSURE_PLATE(), $in));
$this->mapStairs(Ids::DARK_OAK_STAIRS, fn() => Blocks::DARK_OAK_STAIRS());
$this->map(Ids::DARK_OAK_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::DARK_OAK_TRAPDOOR(), $in));
$this->mapStairs(Ids::DARK_PRISMARINE_STAIRS, fn() => Blocks::DARK_PRISMARINE_STAIRS());
$this->map(Ids::DARKOAK_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::DARK_OAK_SIGN(), $in));
$this->map(Ids::DARKOAK_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::DARK_OAK_WALL_SIGN(), $in));
$this->map(Ids::DAYLIGHT_DETECTOR, fn(Reader $in) => Helper::decodeDaylightSensor(Blocks::DAYLIGHT_SENSOR(), $in)
->setInverted(false));
$this->map(Ids::DAYLIGHT_DETECTOR_INVERTED, fn(Reader $in) => Helper::decodeDaylightSensor(Blocks::DAYLIGHT_SENSOR(), $in)
@ -839,7 +969,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
return Blocks::FARMLAND()
->setWetness($in->readBoundedInt(StateNames::MOISTURIZED_AMOUNT, 0, 7));
});
$this->map(Ids::FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::OAK_FENCE_GATE(), $in));
$this->map(Ids::FIRE, function(Reader $in) : Block{
return Blocks::FIRE()
->setAge($in->readBoundedInt(StateNames::AGE, 0, 15));
@ -860,6 +989,7 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
->setFacing($in->readHorizontalFacing())
->setLit(false);
});
$this->map(Ids::GLOW_LICHEN, fn(Reader $in) => Blocks::GLOW_LICHEN()->setFaces($in->readFacingFlags()));
$this->map(Ids::GLOW_FRAME, fn(Reader $in) => Helper::decodeItemFrame(Blocks::GLOWING_ITEM_FRAME(), $in));
$this->map(Ids::GOLDEN_RAIL, function(Reader $in) : Block{
return Blocks::POWERED_RAIL()
@ -887,14 +1017,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
});
$this->map(Ids::IRON_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::IRON_DOOR(), $in));
$this->map(Ids::IRON_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::IRON_TRAPDOOR(), $in));
$this->map(Ids::JUNGLE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::JUNGLE_BUTTON(), $in));
$this->map(Ids::JUNGLE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::JUNGLE_DOOR(), $in));
$this->map(Ids::JUNGLE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::JUNGLE_FENCE_GATE(), $in));
$this->map(Ids::JUNGLE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::JUNGLE_PRESSURE_PLATE(), $in));
$this->mapStairs(Ids::JUNGLE_STAIRS, fn() => Blocks::JUNGLE_STAIRS());
$this->map(Ids::JUNGLE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::JUNGLE_SIGN(), $in));
$this->map(Ids::JUNGLE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::JUNGLE_TRAPDOOR(), $in));
$this->map(Ids::JUNGLE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::JUNGLE_WALL_SIGN(), $in));
$this->map(Ids::LADDER, function(Reader $in) : Block{
return Blocks::LADDER()
->setFacing($in->readHorizontalFacing());
@ -904,18 +1026,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
->setHanging($in->readBool(StateNames::HANGING));
});
$this->map(Ids::LAVA, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::LAVA(), $in));
$this->map(Ids::LEAVES, fn(Reader $in) => Helper::decodeLeaves(match($type = $in->readString(StateNames::OLD_LEAF_TYPE)){
StringValues::OLD_LEAF_TYPE_BIRCH => Blocks::BIRCH_LEAVES(),
StringValues::OLD_LEAF_TYPE_JUNGLE => Blocks::JUNGLE_LEAVES(),
StringValues::OLD_LEAF_TYPE_OAK => Blocks::OAK_LEAVES(),
StringValues::OLD_LEAF_TYPE_SPRUCE => Blocks::SPRUCE_LEAVES(),
default => throw $in->badValueException(StateNames::OLD_LEAF_TYPE, $type),
}, $in));
$this->map(Ids::LEAVES2, fn(Reader $in) => Helper::decodeLeaves(match($type = $in->readString(StateNames::NEW_LEAF_TYPE)){
StringValues::NEW_LEAF_TYPE_ACACIA => Blocks::ACACIA_LEAVES(),
StringValues::NEW_LEAF_TYPE_DARK_OAK => Blocks::DARK_OAK_LEAVES(),
default => throw $in->badValueException(StateNames::NEW_LEAF_TYPE, $type),
}, $in));
$this->map(Ids::LECTERN, function(Reader $in) : Block{
return Blocks::LECTERN()
->setFacing($in->readLegacyHorizontalFacing())
@ -977,21 +1087,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
return Blocks::LOOM()
->setFacing($in->readLegacyHorizontalFacing());
});
$this->map(Ids::MANGROVE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::MANGROVE_BUTTON(), $in));
$this->map(Ids::MANGROVE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::MANGROVE_DOOR(), $in));
$this->mapSlab(Ids::MANGROVE_SLAB, Ids::MANGROVE_DOUBLE_SLAB, fn() => Blocks::MANGROVE_SLAB());
$this->map(Ids::MANGROVE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::MANGROVE_FENCE_GATE(), $in));
$this->map(Ids::MANGROVE_LEAVES, fn(Reader $in) => Helper::decodeLeaves(Blocks::MANGROVE_LEAVES(), $in));
$this->map(Ids::MANGROVE_LOG, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_LOG(), false, $in));
$this->map(Ids::MANGROVE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::MANGROVE_PRESSURE_PLATE(), $in));
$this->mapStairs(Ids::MANGROVE_STAIRS, fn() => Blocks::MANGROVE_STAIRS());
$this->map(Ids::MANGROVE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::MANGROVE_SIGN(), $in));
$this->map(Ids::MANGROVE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::MANGROVE_TRAPDOOR(), $in));
$this->map(Ids::MANGROVE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::MANGROVE_WALL_SIGN(), $in));
$this->map(Ids::MANGROVE_WOOD, function(Reader $in){
$in->ignored(StateNames::STRIPPED_BIT); //this is also ignored by vanilla
return Helper::decodeLog(Blocks::MANGROVE_WOOD(), false, $in);
});
$this->map(Ids::MELON_STEM, fn(Reader $in) => Helper::decodeStem(Blocks::MELON_STEM(), $in));
$this->map(Ids::MONSTER_EGG, function(Reader $in) : Block{
return match($type = $in->readString(StateNames::MONSTER_EGG_STONE_TYPE)){
@ -1019,24 +1114,12 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
->setAge($in->readBoundedInt(StateNames::AGE, 0, 3));
});
$this->mapStairs(Ids::NORMAL_STONE_STAIRS, fn() => Blocks::STONE_STAIRS());
$this->mapStairs(Ids::OAK_STAIRS, fn() => Blocks::OAK_STAIRS());
$this->map(Ids::OCHRE_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::OCHRE())->setAxis($in->readPillarAxis()));
$this->map(Ids::OXIDIZED_COPPER, fn() => Helper::decodeCopper(Blocks::COPPER(), CopperOxidation::OXIDIZED()));
$this->map(Ids::OXIDIZED_CUT_COPPER, fn() => Helper::decodeCopper(Blocks::CUT_COPPER(), CopperOxidation::OXIDIZED()));
$this->mapSlab(Ids::OXIDIZED_CUT_COPPER_SLAB, Ids::OXIDIZED_DOUBLE_CUT_COPPER_SLAB, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_SLAB(), CopperOxidation::OXIDIZED()));
$this->mapStairs(Ids::OXIDIZED_CUT_COPPER_STAIRS, fn() => Helper::decodeCopper(Blocks::CUT_COPPER_STAIRS(), CopperOxidation::OXIDIZED()));
$this->map(Ids::PEARLESCENT_FROGLIGHT, fn(Reader $in) => Blocks::FROGLIGHT()->setFroglightType(FroglightType::PEARLESCENT())->setAxis($in->readPillarAxis()));
$this->map(Ids::PLANKS, function(Reader $in) : Block{
return match($woodName = $in->readString(StateNames::WOOD_TYPE)){
StringValues::WOOD_TYPE_OAK => Blocks::OAK_PLANKS(),
StringValues::WOOD_TYPE_SPRUCE => Blocks::SPRUCE_PLANKS(),
StringValues::WOOD_TYPE_BIRCH => Blocks::BIRCH_PLANKS(),
StringValues::WOOD_TYPE_JUNGLE => Blocks::JUNGLE_PLANKS(),
StringValues::WOOD_TYPE_ACACIA => Blocks::ACACIA_PLANKS(),
StringValues::WOOD_TYPE_DARK_OAK => Blocks::DARK_OAK_PLANKS(),
default => throw $in->badValueException(StateNames::WOOD_TYPE, $woodName),
};
});
$this->mapStairs(Ids::POLISHED_ANDESITE_STAIRS, fn() => Blocks::POLISHED_ANDESITE_STAIRS());
$this->map(Ids::POLISHED_BASALT, function(Reader $in) : Block{
return Blocks::POLISHED_BASALT()
@ -1241,14 +1324,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
default => throw $in->badValueException(StateNames::SPONGE_TYPE, $type),
});
});
$this->map(Ids::SPRUCE_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::SPRUCE_BUTTON(), $in));
$this->map(Ids::SPRUCE_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::SPRUCE_DOOR(), $in));
$this->map(Ids::SPRUCE_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::SPRUCE_FENCE_GATE(), $in));
$this->map(Ids::SPRUCE_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::SPRUCE_PRESSURE_PLATE(), $in));
$this->mapStairs(Ids::SPRUCE_STAIRS, fn() => Blocks::SPRUCE_STAIRS());
$this->map(Ids::SPRUCE_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::SPRUCE_SIGN(), $in));
$this->map(Ids::SPRUCE_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::SPRUCE_TRAPDOOR(), $in));
$this->map(Ids::SPRUCE_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::SPRUCE_WALL_SIGN(), $in));
$this->map(Ids::STAINED_GLASS, function(Reader $in) : Block{
return Blocks::STAINED_GLASS()
->setColor($in->readColor());
@ -1265,7 +1340,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
return Blocks::BANNER()
->setRotation($in->readBoundedInt(StateNames::GROUND_SIGN_DIRECTION, 0, 15));
});
$this->map(Ids::STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::OAK_SIGN(), $in));
$this->map(Ids::STONE, function(Reader $in) : Block{
return match($type = $in->readString(StateNames::STONE_TYPE)){
StringValues::STONE_TYPE_ANDESITE => Blocks::ANDESITE(),
@ -1300,12 +1374,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
return Blocks::STONECUTTER()
->setFacing($in->readHorizontalFacing());
});
$this->map(Ids::STRIPPED_CRIMSON_HYPHAE, fn(Reader $in) => Helper::decodeLog(Blocks::CRIMSON_HYPHAE(), true, $in));
$this->map(Ids::STRIPPED_CRIMSON_STEM, fn(Reader $in) => Helper::decodeLog(Blocks::CRIMSON_STEM(), true, $in));
$this->map(Ids::STRIPPED_MANGROVE_LOG, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_LOG(), true, $in));
$this->map(Ids::STRIPPED_MANGROVE_WOOD, fn(Reader $in) => Helper::decodeLog(Blocks::MANGROVE_WOOD(), true, $in));
$this->map(Ids::STRIPPED_WARPED_HYPHAE, fn(Reader $in) => Helper::decodeLog(Blocks::WARPED_HYPHAE(), true, $in));
$this->map(Ids::STRIPPED_WARPED_STEM, fn(Reader $in) => Helper::decodeLog(Blocks::WARPED_STEM(), true, $in));
$this->map(Ids::SWEET_BERRY_BUSH, function(Reader $in) : Block{
//berry bush only wants 0-3, but it can be bigger in MCPE due to misuse of GROWTH state which goes up to 7
$growth = $in->readBoundedInt(StateNames::GROWTH, 0, 7);
@ -1328,7 +1396,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
return Blocks::TORCH()
->setFacing($in->readTorchFacing());
});
$this->map(Ids::TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::OAK_TRAPDOOR(), $in));
$this->map(Ids::TRAPPED_CHEST, function(Reader $in) : Block{
return Blocks::TRAPPED_CHEST()
->setFacing($in->readHorizontalFacing());
@ -1375,18 +1442,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
return Blocks::WALL_BANNER()
->setFacing($in->readHorizontalFacing());
});
$this->map(Ids::WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::OAK_WALL_SIGN(), $in));
$this->map(Ids::WARPED_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::WARPED_BUTTON(), $in));
$this->map(Ids::WARPED_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::WARPED_DOOR(), $in));
$this->mapSlab(Ids::WARPED_SLAB, Ids::WARPED_DOUBLE_SLAB, fn() => Blocks::WARPED_SLAB());
$this->map(Ids::WARPED_FENCE_GATE, fn(Reader $in) => Helper::decodeFenceGate(Blocks::WARPED_FENCE_GATE(), $in));
$this->map(Ids::WARPED_HYPHAE, fn(Reader $in) => Helper::decodeLog(Blocks::WARPED_HYPHAE(), false, $in));
$this->map(Ids::WARPED_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::WARPED_PRESSURE_PLATE(), $in));
$this->mapStairs(Ids::WARPED_STAIRS, fn() => Blocks::WARPED_STAIRS());
$this->map(Ids::WARPED_STANDING_SIGN, fn(Reader $in) => Helper::decodeFloorSign(Blocks::WARPED_SIGN(), $in));
$this->map(Ids::WARPED_STEM, fn(Reader $in) => Helper::decodeLog(Blocks::WARPED_STEM(), false, $in));
$this->map(Ids::WARPED_TRAPDOOR, fn(Reader $in) => Helper::decodeTrapdoor(Blocks::WARPED_TRAPDOOR(), $in));
$this->map(Ids::WARPED_WALL_SIGN, fn(Reader $in) => Helper::decodeWallSign(Blocks::WARPED_WALL_SIGN(), $in));
$this->map(Ids::WATER, fn(Reader $in) => Helper::decodeStillLiquid(Blocks::WATER(), $in));
$this->map(Ids::WAXED_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::COPPER(), CopperOxidation::NONE()));
$this->map(Ids::WAXED_CUT_COPPER, fn() => Helper::decodeWaxedCopper(Blocks::CUT_COPPER(), CopperOxidation::NONE()));
@ -1413,19 +1468,6 @@ final class BlockStateToObjectDeserializer implements BlockStateDeserializer{
->setAge($in->readBoundedInt(StateNames::WEEPING_VINES_AGE, 0, 25));
});
$this->map(Ids::WHEAT, fn(Reader $in) => Helper::decodeCrops(Blocks::WHEAT(), $in));
$this->map(Ids::WOOD, fn(Reader $in) : Block => Helper::decodeLog(match($woodType = $in->readString(StateNames::WOOD_TYPE)){
StringValues::WOOD_TYPE_ACACIA => Blocks::ACACIA_WOOD(),
StringValues::WOOD_TYPE_BIRCH => Blocks::BIRCH_WOOD(),
StringValues::WOOD_TYPE_DARK_OAK => Blocks::DARK_OAK_WOOD(),
StringValues::WOOD_TYPE_JUNGLE => Blocks::JUNGLE_WOOD(),
StringValues::WOOD_TYPE_OAK => Blocks::OAK_WOOD(),
StringValues::WOOD_TYPE_SPRUCE => Blocks::SPRUCE_WOOD(),
default => throw $in->badValueException(StateNames::WOOD_TYPE, $woodType),
}, $in->readBool(StateNames::STRIPPED_BIT), $in));
$this->map(Ids::WOODEN_BUTTON, fn(Reader $in) => Helper::decodeButton(Blocks::OAK_BUTTON(), $in));
$this->map(Ids::WOODEN_DOOR, fn(Reader $in) => Helper::decodeDoor(Blocks::OAK_DOOR(), $in));
$this->map(Ids::WOODEN_PRESSURE_PLATE, fn(Reader $in) => Helper::decodeSimplePressurePlate(Blocks::OAK_PRESSURE_PLATE(), $in));
$this->mapSlab(Ids::WOODEN_SLAB, Ids::DOUBLE_WOODEN_SLAB, fn(Reader $in) => Helper::mapWoodenSlabType($in));
}
/** @throws BlockStateDeserializeException */

View File

@ -29,6 +29,7 @@ use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\SlabType;
use pocketmine\block\utils\WallConnectionType;
use pocketmine\block\utils\WoodType;
use pocketmine\data\bedrock\block\BlockLegacyMetadata;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\data\bedrock\block\BlockStateNames;
use pocketmine\data\bedrock\block\BlockStateSerializeException;
@ -39,6 +40,7 @@ use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\nbt\tag\Tag;
use pocketmine\utils\AssumptionFailedError;
final class BlockStateWriter{
@ -88,6 +90,28 @@ final class BlockStateWriter{
return $this;
}
/**
* @param int[] $faces
* @phpstan-param array<int, int> $faces
* @return $this
*/
public function writeFacingFlags(array $faces) : self{
$result = 0;
foreach($faces as $face){
$result |= match($face){
Facing::DOWN => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_DOWN,
Facing::UP => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_UP,
Facing::NORTH => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_NORTH,
Facing::SOUTH => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_SOUTH,
Facing::WEST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_WEST,
Facing::EAST => BlockLegacyMetadata::MULTI_FACE_DIRECTION_FLAG_EAST,
default => throw new AssumptionFailedError("Unhandled face $face")
};
}
return $this->writeInt(BlockStateNames::MULTI_FACE_DIRECTION_BITS, $result);
}
/** @return $this */
public function writeEndRodFacingDirection(int $value) : self{
//end rods are stupid in bedrock and have everything except up/down the wrong way round

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -135,6 +135,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Block(Ids::CAKE, Blocks::CAKE());
$this->map1to1Block(Ids::CAULDRON, Blocks::CAULDRON());
$this->map1to1Block(Ids::CHAIN, Blocks::CHAIN());
$this->map1to1Block(Ids::CHERRY_DOOR, Blocks::CHERRY_DOOR());
$this->map1to1Block(Ids::COMPARATOR, Blocks::REDSTONE_COMPARATOR());
$this->map1to1Block(Ids::CRIMSON_DOOR, Blocks::CRIMSON_DOOR());
$this->map1to1Block(Ids::DARK_OAK_DOOR, Blocks::DARK_OAK_DOOR());
@ -185,6 +186,7 @@ final class ItemSerializerDeserializerRegistrar{
$this->map1to1Item(Ids::CHAINMAIL_HELMET, Items::CHAINMAIL_HELMET());
$this->map1to1Item(Ids::CHAINMAIL_LEGGINGS, Items::CHAINMAIL_LEGGINGS());
$this->map1to1Item(Ids::CHARCOAL, Items::CHARCOAL());
$this->map1to1Item(Ids::CHERRY_SIGN, Items::CHERRY_SIGN());
$this->map1to1Item(Ids::CHICKEN, Items::RAW_CHICKEN());
$this->map1to1Item(Ids::CHORUS_FRUIT, Items::CHORUS_FRUIT());
$this->map1to1Item(Ids::CLAY_BALL, Items::CLAY());

View File

@ -30,20 +30,24 @@ final class ItemTypeNames{
public const ACACIA_BOAT = "minecraft:acacia_boat";
public const ACACIA_CHEST_BOAT = "minecraft:acacia_chest_boat";
public const ACACIA_DOOR = "minecraft:acacia_door";
public const ACACIA_HANGING_SIGN = "minecraft:acacia_hanging_sign";
public const ACACIA_SIGN = "minecraft:acacia_sign";
public const AGENT_SPAWN_EGG = "minecraft:agent_spawn_egg";
public const ALLAY_SPAWN_EGG = "minecraft:allay_spawn_egg";
public const AMETHYST_SHARD = "minecraft:amethyst_shard";
public const ANGLER_POTTERY_SHERD = "minecraft:angler_pottery_sherd";
public const APPLE = "minecraft:apple";
public const ARCHER_POTTERY_SHARD = "minecraft:archer_pottery_shard";
public const ARCHER_POTTERY_SHERD = "minecraft:archer_pottery_sherd";
public const ARMOR_STAND = "minecraft:armor_stand";
public const ARMS_UP_POTTERY_SHARD = "minecraft:arms_up_pottery_shard";
public const ARMS_UP_POTTERY_SHERD = "minecraft:arms_up_pottery_sherd";
public const ARROW = "minecraft:arrow";
public const AXOLOTL_BUCKET = "minecraft:axolotl_bucket";
public const AXOLOTL_SPAWN_EGG = "minecraft:axolotl_spawn_egg";
public const BAKED_POTATO = "minecraft:baked_potato";
public const BALLOON = "minecraft:balloon";
public const BAMBOO_CHEST_RAFT = "minecraft:bamboo_chest_raft";
public const BAMBOO_DOOR = "minecraft:bamboo_door";
public const BAMBOO_HANGING_SIGN = "minecraft:bamboo_hanging_sign";
public const BAMBOO_RAFT = "minecraft:bamboo_raft";
public const BAMBOO_SIGN = "minecraft:bamboo_sign";
public const BANNER = "minecraft:banner";
@ -58,8 +62,10 @@ final class ItemTypeNames{
public const BIRCH_BOAT = "minecraft:birch_boat";
public const BIRCH_CHEST_BOAT = "minecraft:birch_chest_boat";
public const BIRCH_DOOR = "minecraft:birch_door";
public const BIRCH_HANGING_SIGN = "minecraft:birch_hanging_sign";
public const BIRCH_SIGN = "minecraft:birch_sign";
public const BLACK_DYE = "minecraft:black_dye";
public const BLADE_POTTERY_SHERD = "minecraft:blade_pottery_sherd";
public const BLAZE_POWDER = "minecraft:blaze_powder";
public const BLAZE_ROD = "minecraft:blaze_rod";
public const BLAZE_SPAWN_EGG = "minecraft:blaze_spawn_egg";
@ -73,15 +79,18 @@ final class ItemTypeNames{
public const BOW = "minecraft:bow";
public const BOWL = "minecraft:bowl";
public const BREAD = "minecraft:bread";
public const BREWER_POTTERY_SHERD = "minecraft:brewer_pottery_sherd";
public const BREWING_STAND = "minecraft:brewing_stand";
public const BRICK = "minecraft:brick";
public const BROWN_DYE = "minecraft:brown_dye";
public const BRUSH = "minecraft:brush";
public const BUCKET = "minecraft:bucket";
public const BURN_POTTERY_SHERD = "minecraft:burn_pottery_sherd";
public const CAKE = "minecraft:cake";
public const CAMEL_SPAWN_EGG = "minecraft:camel_spawn_egg";
public const CAMERA = "minecraft:camera";
public const CAMPFIRE = "minecraft:campfire";
public const CARPET = "minecraft:carpet";
public const CARROT = "minecraft:carrot";
public const CARROT_ON_A_STICK = "minecraft:carrot_on_a_stick";
public const CAT_SPAWN_EGG = "minecraft:cat_spawn_egg";
@ -93,6 +102,11 @@ final class ItemTypeNames{
public const CHAINMAIL_HELMET = "minecraft:chainmail_helmet";
public const CHAINMAIL_LEGGINGS = "minecraft:chainmail_leggings";
public const CHARCOAL = "minecraft:charcoal";
public const CHERRY_BOAT = "minecraft:cherry_boat";
public const CHERRY_CHEST_BOAT = "minecraft:cherry_chest_boat";
public const CHERRY_DOOR = "minecraft:cherry_door";
public const CHERRY_HANGING_SIGN = "minecraft:cherry_hanging_sign";
public const CHERRY_SIGN = "minecraft:cherry_sign";
public const CHEST_BOAT = "minecraft:chest_boat";
public const CHEST_MINECART = "minecraft:chest_minecart";
public const CHICKEN = "minecraft:chicken";
@ -101,6 +115,7 @@ final class ItemTypeNames{
public const CLAY_BALL = "minecraft:clay_ball";
public const CLOCK = "minecraft:clock";
public const COAL = "minecraft:coal";
public const COAST_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:coast_armor_trim_smithing_template";
public const COCOA_BEANS = "minecraft:cocoa_beans";
public const COD = "minecraft:cod";
public const COD_BUCKET = "minecraft:cod_bucket";
@ -118,18 +133,21 @@ final class ItemTypeNames{
public const COOKED_SALMON = "minecraft:cooked_salmon";
public const COOKIE = "minecraft:cookie";
public const COPPER_INGOT = "minecraft:copper_ingot";
public const CORAL = "minecraft:coral";
public const COW_SPAWN_EGG = "minecraft:cow_spawn_egg";
public const CREEPER_BANNER_PATTERN = "minecraft:creeper_banner_pattern";
public const CREEPER_SPAWN_EGG = "minecraft:creeper_spawn_egg";
public const CRIMSON_DOOR = "minecraft:crimson_door";
public const CRIMSON_HANGING_SIGN = "minecraft:crimson_hanging_sign";
public const CRIMSON_SIGN = "minecraft:crimson_sign";
public const CROSSBOW = "minecraft:crossbow";
public const CYAN_DYE = "minecraft:cyan_dye";
public const DANGER_POTTERY_SHERD = "minecraft:danger_pottery_sherd";
public const DARK_OAK_BOAT = "minecraft:dark_oak_boat";
public const DARK_OAK_CHEST_BOAT = "minecraft:dark_oak_chest_boat";
public const DARK_OAK_DOOR = "minecraft:dark_oak_door";
public const DARK_OAK_HANGING_SIGN = "minecraft:dark_oak_hanging_sign";
public const DARK_OAK_SIGN = "minecraft:dark_oak_sign";
public const DEBUG_STICK = "minecraft:debug_stick";
public const DIAMOND = "minecraft:diamond";
public const DIAMOND_AXE = "minecraft:diamond_axe";
public const DIAMOND_BOOTS = "minecraft:diamond_boots";
@ -147,6 +165,7 @@ final class ItemTypeNames{
public const DRAGON_BREATH = "minecraft:dragon_breath";
public const DRIED_KELP = "minecraft:dried_kelp";
public const DROWNED_SPAWN_EGG = "minecraft:drowned_spawn_egg";
public const DUNE_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:dune_armor_trim_smithing_template";
public const DYE = "minecraft:dye";
public const ECHO_SHARD = "minecraft:echo_shard";
public const EGG = "minecraft:egg";
@ -164,7 +183,10 @@ final class ItemTypeNames{
public const ENDERMITE_SPAWN_EGG = "minecraft:endermite_spawn_egg";
public const EVOKER_SPAWN_EGG = "minecraft:evoker_spawn_egg";
public const EXPERIENCE_BOTTLE = "minecraft:experience_bottle";
public const EXPLORER_POTTERY_SHERD = "minecraft:explorer_pottery_sherd";
public const EYE_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:eye_armor_trim_smithing_template";
public const FEATHER = "minecraft:feather";
public const FENCE = "minecraft:fence";
public const FERMENTED_SPIDER_EYE = "minecraft:fermented_spider_eye";
public const FIELD_MASONED_BANNER_PATTERN = "minecraft:field_masoned_banner_pattern";
public const FILLED_MAP = "minecraft:filled_map";
@ -178,6 +200,7 @@ final class ItemTypeNames{
public const FLOWER_POT = "minecraft:flower_pot";
public const FOX_SPAWN_EGG = "minecraft:fox_spawn_egg";
public const FRAME = "minecraft:frame";
public const FRIEND_POTTERY_SHERD = "minecraft:friend_pottery_sherd";
public const FROG_SPAWN_EGG = "minecraft:frog_spawn_egg";
public const GHAST_SPAWN_EGG = "minecraft:ghast_spawn_egg";
public const GHAST_TEAR = "minecraft:ghast_tear";
@ -211,12 +234,16 @@ final class ItemTypeNames{
public const GUARDIAN_SPAWN_EGG = "minecraft:guardian_spawn_egg";
public const GUNPOWDER = "minecraft:gunpowder";
public const HEART_OF_THE_SEA = "minecraft:heart_of_the_sea";
public const HEART_POTTERY_SHERD = "minecraft:heart_pottery_sherd";
public const HEARTBREAK_POTTERY_SHERD = "minecraft:heartbreak_pottery_sherd";
public const HOGLIN_SPAWN_EGG = "minecraft:hoglin_spawn_egg";
public const HONEY_BOTTLE = "minecraft:honey_bottle";
public const HONEYCOMB = "minecraft:honeycomb";
public const HOPPER = "minecraft:hopper";
public const HOPPER_MINECART = "minecraft:hopper_minecart";
public const HORSE_SPAWN_EGG = "minecraft:horse_spawn_egg";
public const HOST_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:host_armor_trim_smithing_template";
public const HOWL_POTTERY_SHERD = "minecraft:howl_pottery_sherd";
public const HUSK_SPAWN_EGG = "minecraft:husk_spawn_egg";
public const ICE_BOMB = "minecraft:ice_bomb";
public const INK_SAC = "minecraft:ink_sac";
@ -237,6 +264,7 @@ final class ItemTypeNames{
public const JUNGLE_BOAT = "minecraft:jungle_boat";
public const JUNGLE_CHEST_BOAT = "minecraft:jungle_chest_boat";
public const JUNGLE_DOOR = "minecraft:jungle_door";
public const JUNGLE_HANGING_SIGN = "minecraft:jungle_hanging_sign";
public const JUNGLE_SIGN = "minecraft:jungle_sign";
public const KELP = "minecraft:kelp";
public const LAPIS_LAZULI = "minecraft:lapis_lazuli";
@ -254,20 +282,25 @@ final class ItemTypeNames{
public const LINGERING_POTION = "minecraft:lingering_potion";
public const LLAMA_SPAWN_EGG = "minecraft:llama_spawn_egg";
public const LODESTONE_COMPASS = "minecraft:lodestone_compass";
public const LOG = "minecraft:log";
public const LOG2 = "minecraft:log2";
public const MAGENTA_DYE = "minecraft:magenta_dye";
public const MAGMA_CREAM = "minecraft:magma_cream";
public const MAGMA_CUBE_SPAWN_EGG = "minecraft:magma_cube_spawn_egg";
public const MANGROVE_BOAT = "minecraft:mangrove_boat";
public const MANGROVE_CHEST_BOAT = "minecraft:mangrove_chest_boat";
public const MANGROVE_DOOR = "minecraft:mangrove_door";
public const MANGROVE_HANGING_SIGN = "minecraft:mangrove_hanging_sign";
public const MANGROVE_SIGN = "minecraft:mangrove_sign";
public const MEDICINE = "minecraft:medicine";
public const MELON_SEEDS = "minecraft:melon_seeds";
public const MELON_SLICE = "minecraft:melon_slice";
public const MILK_BUCKET = "minecraft:milk_bucket";
public const MINECART = "minecraft:minecart";
public const MINER_POTTERY_SHERD = "minecraft:miner_pottery_sherd";
public const MOJANG_BANNER_PATTERN = "minecraft:mojang_banner_pattern";
public const MOOSHROOM_SPAWN_EGG = "minecraft:mooshroom_spawn_egg";
public const MOURNER_POTTERY_SHERD = "minecraft:mourner_pottery_sherd";
public const MULE_SPAWN_EGG = "minecraft:mule_spawn_egg";
public const MUSHROOM_STEW = "minecraft:mushroom_stew";
public const MUSIC_DISC_11 = "minecraft:music_disc_11";
@ -281,6 +314,7 @@ final class ItemTypeNames{
public const MUSIC_DISC_MELLOHI = "minecraft:music_disc_mellohi";
public const MUSIC_DISC_OTHERSIDE = "minecraft:music_disc_otherside";
public const MUSIC_DISC_PIGSTEP = "minecraft:music_disc_pigstep";
public const MUSIC_DISC_RELIC = "minecraft:music_disc_relic";
public const MUSIC_DISC_STAL = "minecraft:music_disc_stal";
public const MUSIC_DISC_STRAD = "minecraft:music_disc_strad";
public const MUSIC_DISC_WAIT = "minecraft:music_disc_wait";
@ -303,9 +337,11 @@ final class ItemTypeNames{
public const NETHERITE_SCRAP = "minecraft:netherite_scrap";
public const NETHERITE_SHOVEL = "minecraft:netherite_shovel";
public const NETHERITE_SWORD = "minecraft:netherite_sword";
public const NETHERITE_UPGRADE_SMITHING_TEMPLATE = "minecraft:netherite_upgrade_smithing_template";
public const NPC_SPAWN_EGG = "minecraft:npc_spawn_egg";
public const OAK_BOAT = "minecraft:oak_boat";
public const OAK_CHEST_BOAT = "minecraft:oak_chest_boat";
public const OAK_HANGING_SIGN = "minecraft:oak_hanging_sign";
public const OAK_SIGN = "minecraft:oak_sign";
public const OCELOT_SPAWN_EGG = "minecraft:ocelot_spawn_egg";
public const ORANGE_DYE = "minecraft:orange_dye";
@ -321,6 +357,8 @@ final class ItemTypeNames{
public const PIGLIN_SPAWN_EGG = "minecraft:piglin_spawn_egg";
public const PILLAGER_SPAWN_EGG = "minecraft:pillager_spawn_egg";
public const PINK_DYE = "minecraft:pink_dye";
public const PITCHER_POD = "minecraft:pitcher_pod";
public const PLENTY_POTTERY_SHERD = "minecraft:plenty_pottery_sherd";
public const POISONOUS_POTATO = "minecraft:poisonous_potato";
public const POLAR_BEAR_SPAWN_EGG = "minecraft:polar_bear_spawn_egg";
public const POPPED_CHORUS_FRUIT = "minecraft:popped_chorus_fruit";
@ -330,7 +368,7 @@ final class ItemTypeNames{
public const POWDER_SNOW_BUCKET = "minecraft:powder_snow_bucket";
public const PRISMARINE_CRYSTALS = "minecraft:prismarine_crystals";
public const PRISMARINE_SHARD = "minecraft:prismarine_shard";
public const PRIZE_POTTERY_SHARD = "minecraft:prize_pottery_shard";
public const PRIZE_POTTERY_SHERD = "minecraft:prize_pottery_sherd";
public const PUFFERFISH = "minecraft:pufferfish";
public const PUFFERFISH_BUCKET = "minecraft:pufferfish_bucket";
public const PUFFERFISH_SPAWN_EGG = "minecraft:pufferfish_spawn_egg";
@ -343,6 +381,7 @@ final class ItemTypeNames{
public const RABBIT_HIDE = "minecraft:rabbit_hide";
public const RABBIT_SPAWN_EGG = "minecraft:rabbit_spawn_egg";
public const RABBIT_STEW = "minecraft:rabbit_stew";
public const RAISER_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:raiser_armor_trim_smithing_template";
public const RAPID_FERTILIZER = "minecraft:rapid_fertilizer";
public const RAVAGER_SPAWN_EGG = "minecraft:ravager_spawn_egg";
public const RAW_COPPER = "minecraft:raw_copper";
@ -352,26 +391,34 @@ final class ItemTypeNames{
public const RED_DYE = "minecraft:red_dye";
public const REDSTONE = "minecraft:redstone";
public const REPEATER = "minecraft:repeater";
public const RIB_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:rib_armor_trim_smithing_template";
public const ROTTEN_FLESH = "minecraft:rotten_flesh";
public const SADDLE = "minecraft:saddle";
public const SALMON = "minecraft:salmon";
public const SALMON_BUCKET = "minecraft:salmon_bucket";
public const SALMON_SPAWN_EGG = "minecraft:salmon_spawn_egg";
public const SCUTE = "minecraft:scute";
public const SENTRY_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:sentry_armor_trim_smithing_template";
public const SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:shaper_armor_trim_smithing_template";
public const SHEAF_POTTERY_SHERD = "minecraft:sheaf_pottery_sherd";
public const SHEARS = "minecraft:shears";
public const SHEEP_SPAWN_EGG = "minecraft:sheep_spawn_egg";
public const SHELTER_POTTERY_SHERD = "minecraft:shelter_pottery_sherd";
public const SHIELD = "minecraft:shield";
public const SHULKER_SHELL = "minecraft:shulker_shell";
public const SHULKER_SPAWN_EGG = "minecraft:shulker_spawn_egg";
public const SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:silence_armor_trim_smithing_template";
public const SILVERFISH_SPAWN_EGG = "minecraft:silverfish_spawn_egg";
public const SKELETON_HORSE_SPAWN_EGG = "minecraft:skeleton_horse_spawn_egg";
public const SKELETON_SPAWN_EGG = "minecraft:skeleton_spawn_egg";
public const SKULL = "minecraft:skull";
public const SKULL_BANNER_PATTERN = "minecraft:skull_banner_pattern";
public const SKULL_POTTERY_SHARD = "minecraft:skull_pottery_shard";
public const SKULL_POTTERY_SHERD = "minecraft:skull_pottery_sherd";
public const SLIME_BALL = "minecraft:slime_ball";
public const SLIME_SPAWN_EGG = "minecraft:slime_spawn_egg";
public const SNIFFER_SPAWN_EGG = "minecraft:sniffer_spawn_egg";
public const SNORT_POTTERY_SHERD = "minecraft:snort_pottery_sherd";
public const SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:snout_armor_trim_smithing_template";
public const SNOW_GOLEM_SPAWN_EGG = "minecraft:snow_golem_spawn_egg";
public const SNOWBALL = "minecraft:snowball";
public const SOUL_CAMPFIRE = "minecraft:soul_campfire";
@ -379,10 +426,12 @@ final class ItemTypeNames{
public const SPAWN_EGG = "minecraft:spawn_egg";
public const SPIDER_EYE = "minecraft:spider_eye";
public const SPIDER_SPAWN_EGG = "minecraft:spider_spawn_egg";
public const SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:spire_armor_trim_smithing_template";
public const SPLASH_POTION = "minecraft:splash_potion";
public const SPRUCE_BOAT = "minecraft:spruce_boat";
public const SPRUCE_CHEST_BOAT = "minecraft:spruce_chest_boat";
public const SPRUCE_DOOR = "minecraft:spruce_door";
public const SPRUCE_HANGING_SIGN = "minecraft:spruce_hanging_sign";
public const SPRUCE_SIGN = "minecraft:spruce_sign";
public const SPYGLASS = "minecraft:spyglass";
public const SQUID_SPAWN_EGG = "minecraft:squid_spawn_egg";
@ -401,6 +450,7 @@ final class ItemTypeNames{
public const SWEET_BERRIES = "minecraft:sweet_berries";
public const TADPOLE_BUCKET = "minecraft:tadpole_bucket";
public const TADPOLE_SPAWN_EGG = "minecraft:tadpole_spawn_egg";
public const TIDE_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:tide_armor_trim_smithing_template";
public const TNT_MINECART = "minecraft:tnt_minecart";
public const TORCHFLOWER_SEEDS = "minecraft:torchflower_seeds";
public const TOTEM_OF_UNDYING = "minecraft:totem_of_undying";
@ -411,18 +461,23 @@ final class ItemTypeNames{
public const TROPICAL_FISH_SPAWN_EGG = "minecraft:tropical_fish_spawn_egg";
public const TURTLE_HELMET = "minecraft:turtle_helmet";
public const TURTLE_SPAWN_EGG = "minecraft:turtle_spawn_egg";
public const VEX_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:vex_armor_trim_smithing_template";
public const VEX_SPAWN_EGG = "minecraft:vex_spawn_egg";
public const VILLAGER_SPAWN_EGG = "minecraft:villager_spawn_egg";
public const VINDICATOR_SPAWN_EGG = "minecraft:vindicator_spawn_egg";
public const WANDERING_TRADER_SPAWN_EGG = "minecraft:wandering_trader_spawn_egg";
public const WARD_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:ward_armor_trim_smithing_template";
public const WARDEN_SPAWN_EGG = "minecraft:warden_spawn_egg";
public const WARPED_DOOR = "minecraft:warped_door";
public const WARPED_FUNGUS_ON_A_STICK = "minecraft:warped_fungus_on_a_stick";
public const WARPED_HANGING_SIGN = "minecraft:warped_hanging_sign";
public const WARPED_SIGN = "minecraft:warped_sign";
public const WATER_BUCKET = "minecraft:water_bucket";
public const WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:wayfinder_armor_trim_smithing_template";
public const WHEAT = "minecraft:wheat";
public const WHEAT_SEEDS = "minecraft:wheat_seeds";
public const WHITE_DYE = "minecraft:white_dye";
public const WILD_ARMOR_TRIM_SMITHING_TEMPLATE = "minecraft:wild_armor_trim_smithing_template";
public const WITCH_SPAWN_EGG = "minecraft:witch_spawn_egg";
public const WITHER_SKELETON_SPAWN_EGG = "minecraft:wither_skeleton_spawn_egg";
public const WITHER_SPAWN_EGG = "minecraft:wither_spawn_egg";

View File

@ -36,6 +36,11 @@ interface RuntimeDataDescriber extends RuntimeEnumDescriber{
public function horizontalFacing(int &$facing) : void;
/**
* @param int[] $faces
*/
public function facingFlags(array &$faces) : void;
/**
* @param int[] $faces
*/

View File

@ -86,6 +86,20 @@ final class RuntimeDataReader implements RuntimeDataDescriber{
};
}
/**
* @param int[] $faces
*/
public function facingFlags(array &$faces) : void{
$result = [];
foreach(Facing::ALL as $facing){
if($this->readBool()){
$result[$facing] = $facing;
}
}
$faces = $result;
}
/**
* @param int[] $faces
*/

View File

@ -56,6 +56,10 @@ final class RuntimeDataSizeCalculator implements RuntimeDataDescriber{
$this->addBits(2);
}
public function facingFlags(array &$faces) : void{
$this->addBits(count(Facing::ALL));
}
public function horizontalFacingFlags(array &$faces) : void{
$this->addBits(count(Facing::HORIZONTAL));
}

View File

@ -85,6 +85,16 @@ final class RuntimeDataWriter implements RuntimeDataDescriber{
});
}
/**
* @param int[] $faces
*/
public function facingFlags(array &$faces) : void{
$uniqueFaces = array_flip($faces);
foreach(Facing::ALL as $facing){
$this->writeBool(isset($uniqueFaces[$facing]));
}
}
/**
* @param int[] $faces
*/

View File

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

View File

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

View File

@ -302,8 +302,9 @@ final class ItemTypeIds{
public const MEDICINE = 20263;
public const MANGROVE_BOAT = 20264;
public const GLOW_BERRIES = 20265;
public const CHERRY_SIGN = 20266;
public const FIRST_UNUSED_ITEM_ID = 20266;
public const FIRST_UNUSED_ITEM_ID = 20267;
private static int $nextDynamicId = self::FIRST_UNUSED_ITEM_ID;

View File

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

View File

@ -114,6 +114,7 @@ use pocketmine\world\World;
* @method static Item CHEMICAL_SULPHATE()
* @method static Item CHEMICAL_TUNGSTEN_CHLORIDE()
* @method static Item CHEMICAL_WATER()
* @method static ItemBlockWallOrFloor CHERRY_SIGN()
* @method static ChorusFruit CHORUS_FRUIT()
* @method static Item CLAY()
* @method static Clock CLOCK()
@ -362,6 +363,7 @@ final class VanillaItems{
self::register("bucket", new Bucket(new IID(Ids::BUCKET), "Bucket"));
self::register("carrot", new Carrot(new IID(Ids::CARROT), "Carrot"));
self::register("charcoal", new Coal(new IID(Ids::CHARCOAL), "Charcoal"));
self::register("cherry_sign", new ItemBlockWallOrFloor(new IID(Ids::CHERRY_SIGN), Blocks::CHERRY_SIGN(), Blocks::CHERRY_WALL_SIGN()));
self::register("chemical_aluminium_oxide", new Item(new IID(Ids::CHEMICAL_ALUMINIUM_OXIDE), "Aluminium Oxide"));
self::register("chemical_ammonia", new Item(new IID(Ids::CHEMICAL_AMMONIA), "Ammonia"));
self::register("chemical_barium_sulphate", new Item(new IID(Ids::CHEMICAL_BARIUM_SULPHATE), "Barium Sulphate"));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -70,7 +70,7 @@ final class GlobalBlockStateHandlers{
BlockIdMetaUpgrader::loadFromString(
Filesystem::fileGetContents(Path::join(
BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH,
'1.12.0_to_1.18.10_blockstate_map.bin'
'id_meta_to_nbt/1.12.0.bin'
)),
LegacyBlockIdToStringIdMap::getInstance(),
$blockStateUpgrader

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\block;
use PHPUnit\Framework\TestCase;
use pocketmine\utils\Utils;
use function array_unique;
use function max;
@ -43,4 +44,14 @@ class BlockTypeIdsTest extends TestCase{
self::assertSameSize($idTable, array_unique($idTable), "Every BlockTypeID must be unique");
}
public function testVanillaBlocksParity() : void{
$reflect = new \ReflectionClass(BlockTypeIds::class);
foreach(Utils::stringifyKeys(VanillaBlocks::getAll()) as $name => $block){
$expected = $block->getTypeId();
$actual = $reflect->getConstant($name);
self::assertSame($expected, $actual, "VanillaBlocks::$name() does not match BlockTypeIds::$name");
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\item;
use PHPUnit\Framework\TestCase;
use pocketmine\utils\Utils;
use function array_unique;
use function max;
@ -43,4 +44,17 @@ class ItemTypeIdsTest extends TestCase{
self::assertSameSize($idTable, array_unique($idTable), "Every ItemTypeID must be unique");
}
public function testVanillaItemsParity() : void{
$reflect = new \ReflectionClass(ItemTypeIds::class);
foreach(Utils::stringifyKeys(VanillaItems::getAll()) as $name => $item){
if($item instanceof ItemBlock){
continue;
}
$expected = $item->getTypeId();
$actual = $reflect->getConstant($name);
self::assertSame($expected, $actual, "VanillaItems::$name() type ID does not match ItemTypeIds::$name");
}
}
}

View File

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