Created
August 18, 2025 19:35
-
-
Save dktapps/3ccf09680113ffb573e409c802827fcd to your computer and use it in GitHub Desktop.
Proof of concept for a cursed hack for Minecraft flattened blocks in PocketMine-MP, based on `unify-block-serializers` branch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
use pocketmine\block\Block; | |
use pocketmine\block\CakeWithDyedCandle; | |
use pocketmine\block\Coral; | |
use pocketmine\block\utils\CoralType; | |
use pocketmine\block\utils\DyeColor; | |
use pocketmine\block\VanillaBlocks as Blocks; | |
use pocketmine\block\Wool; | |
use pocketmine\data\bedrock\block\BlockStateNames; | |
use pocketmine\data\bedrock\block\convert\BlockStateReader; | |
use pocketmine\data\bedrock\block\convert\BlockStateWriter; | |
use pocketmine\data\bedrock\block\convert\property\BoolProperty; | |
use pocketmine\data\bedrock\block\convert\property\EnumProperty; | |
use pocketmine\data\bedrock\block\convert\property\Property; | |
use pocketmine\data\bedrock\block\convert\ValueMappings; | |
require __DIR__ .'/vendor/autoload.php'; | |
/** | |
* @param string[]|EnumProperty[] $components | |
* @phpstan-param list<string|EnumProperty<*, *>> $components | |
* | |
* @return string[][] | |
*/ | |
function compilePermutations(array $components) : array{ | |
$result = []; | |
foreach($components as $component){ | |
$column = is_string($component) ? [$component] : array_keys($component->getEnumMap()->getValueToEnum()); | |
if(count($result) === 0){ | |
$result[] = $column; | |
}else{ | |
$stepResult = []; | |
foreach($result as $parts){ | |
foreach($column as $value){ | |
$stepPart = $parts; | |
$stepPart[] = $value; | |
$stepResult[] = $stepPart; | |
} | |
} | |
$result = $stepResult; | |
} | |
} | |
return $result; | |
} | |
/** | |
* @param string[]|EnumProperty[] $idComponents | |
* @param Property[] $properties | |
* | |
* @phpstan-template TBlock of Block | |
* @phpstan-param TBlock $block | |
* @phpstan-param list<string|EnumProperty<TBlock, *>> $idComponents | |
* @phpstan-param list<Property<TBlock>> $properties | |
*/ | |
function mapMatrixFlattened( | |
Block $block, | |
array $idComponents, | |
array $properties = [] | |
) : void{ | |
//This is a really cursed hack that lets us essentially write flattened properties as blockstate properties, and | |
//then pull them out to compile an ID :D | |
//This works surprisingly well and is much more elegant than I would've expected | |
$idProperties = array_filter($idComponents, fn($c) => !is_string($c)); | |
$serializer = function(Block $block) use ($idComponents, $idProperties, $properties) : BlockStateWriter{ | |
//serialize properties into the ID | |
$idWriter = new BlockStateWriter("dummy"); | |
foreach($idProperties as $idProperty){ | |
$idProperty->serialize($block, $idWriter); | |
} | |
$flattenedReader = new BlockStateReader($idWriter->getBlockStateData()); | |
$id = ""; | |
foreach($idComponents as $infix){ | |
$id .= is_string($infix) ? $infix : $flattenedReader->readString($infix->getName()); | |
} | |
//serialize actual properties | |
$realWriter = new BlockStateWriter($id); | |
foreach($properties as $property){ | |
$property->serialize($block, $realWriter); | |
} | |
return $realWriter; | |
}; | |
$deserializers = []; | |
foreach(compilePermutations($idComponents) as $idParts){ | |
//deconstruct the ID into a fake state | |
//we can do this at registration time since there will be multiple deserializers | |
$id = implode("", $idParts); | |
$flattenedWriter = new BlockStateWriter("dummy"); | |
foreach($idComponents as $k => $component){ | |
if($component instanceof EnumProperty){ | |
$fakeValue = $idParts[$k]; | |
$flattenedWriter->writeString($component->getName(), $fakeValue); | |
} | |
} | |
$idReader = new BlockStateReader($flattenedWriter->getBlockStateData()); | |
echo $id . ": " . $flattenedWriter->getBlockStateData()->toVanillaNbt() . "\n"; | |
$deserializers[$id] = function(BlockStateReader $reader) use ($block, $idReader, $idProperties, $properties) : Block{ | |
//deserialize properties from the ID | |
$block = clone $block; | |
foreach($idProperties as $component){ | |
$component->deserialize($block, $idReader); | |
} | |
//deserialize actual properties | |
foreach($properties as $property){ | |
$property->deserialize($block, $reader); | |
} | |
return $block; | |
}; | |
} | |
} | |
enum Bool_{ | |
case FALSE; | |
case TRUE; | |
} | |
ValueMappings::getInstance()->addEnum(Bool_::class, fn(Bool_ $v) => match($v){ | |
Bool_::FALSE => "", | |
Bool_::TRUE => "dead_" | |
}); | |
ValueMappings::getInstance()->addEnum(CoralType::class, fn(CoralType $v) => match($v){ | |
CoralType::TUBE => "tube", | |
CoralType::BRAIN => "brain", | |
CoralType::BUBBLE => "bubble", | |
CoralType::FIRE => "fire", | |
CoralType::HORN => "horn" | |
}); | |
mapMatrixFlattened( | |
block: Blocks::CORAL(), | |
idComponents: [ | |
"minecraft:", | |
new EnumProperty("dead", Bool_::class, fn(Coral $b) => $b->isDead() ? Bool_::TRUE : Bool_::FALSE, fn(Coral $b, Bool_ $v) => $b->setDead($v === Bool_::TRUE)), | |
new EnumProperty("coral_type", CoralType::class, fn(Coral $b) => $b->getCoralType(), fn(Coral $b, CoralType $v) => $b->setCoralType($v)), | |
"_coral" | |
] | |
); | |
mapMatrixFlattened( | |
block: Blocks::WOOL(), | |
idComponents: [ | |
"minecraft:", | |
new EnumProperty("color", DyeColor::class, fn(Wool $b) => $b->getColor(), fn(Wool $b, DyeColor $v) => $b->setColor($v)), | |
"_wool" | |
] | |
); | |
mapMatrixFlattened( | |
block: Blocks::CAKE_WITH_DYED_CANDLE(), | |
idComponents: [ | |
"minecraft:", | |
new EnumProperty("color", DyeColor::class, fn(CakeWithDyedCandle $b) => $b->getColor(), fn(CakeWithDyedCandle $b, DyeColor $v) => $b->setColor($v)), | |
"_candle_cake" | |
], | |
properties: [ | |
new BoolProperty(BlockStateNames::LIT, fn(CakeWithDyedCandle $b) => $b->isLit(), fn(CakeWithDyedCandle $b, bool $v) => $b->setLit($v)) | |
] | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment