ItemBlock: reference blocks directly (take 2)

This was first attempted in f64dc01bd1c14ff3f79bd6c18d0c337dbc0e87e0, but reverted, since I hadn't considered how to handle stripping state data from blocks.

This removes the abusable API RuntimeBlockStateRegistry::fromTypeId() and related methods. These were only used to allow ItemBlocks to magically start referencing other blocks if the blocks were overridden by a plugin, but this was never a well-supported use-case anyway.

Instead of relying on RuntimeBlockStateRegistry, we remember the state that the block had during its constructor, and use that to normalize the non-item properties for asItem().

closes #5609
This commit is contained in:
Dylan K. Taylor 2023-04-13 12:44:47 +01:00
parent 1c626baf1a
commit 874fdf5adb
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
18 changed files with 57 additions and 83 deletions

View File

@ -51,7 +51,7 @@ class Anvil extends Transparent implements Fallable{
private int $damage = self::UNDAMAGED; private int $damage = self::UNDAMAGED;
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->boundedInt(2, self::UNDAMAGED, self::VERY_DAMAGED, $this->damage); $w->boundedInt(2, self::UNDAMAGED, self::VERY_DAMAGED, $this->damage);
} }

View File

@ -76,6 +76,8 @@ class Block{
*/ */
private static array $stateDataBits = []; private static array $stateDataBits = [];
private int $defaultStateData;
/** /**
* @param string $name English name of the block type (TODO: implement translations) * @param string $name English name of the block type (TODO: implement translations)
*/ */
@ -84,6 +86,8 @@ class Block{
$this->fallbackName = $name; $this->fallbackName = $name;
$this->typeInfo = $typeInfo; $this->typeInfo = $typeInfo;
$this->position = new Position(0, 0, 0, null); $this->position = new Position(0, 0, 0, null);
$this->defaultStateData = $this->computeStateData();
} }
public function __clone(){ public function __clone(){
@ -174,7 +178,9 @@ class Block{
* Type information such as colour, wood type, etc. is preserved. * Type information such as colour, wood type, etc. is preserved.
*/ */
public function asItem() : Item{ public function asItem() : Item{
return new ItemBlock($this); $normalized = clone $this;
$normalized->decodeStateData($this->defaultStateData);
return new ItemBlock($normalized);
} }
final public function getRequiredTypeDataBits() : int{ final public function getRequiredTypeDataBits() : int{
@ -211,6 +217,17 @@ class Block{
} }
} }
private function decodeStateData(int $data) : void{
$stateBits = $this->getRequiredStateDataBits();
$reader = new RuntimeDataReader($stateBits, $data);
$this->describeState($reader);
$readBits = $reader->getOffset();
if($stateBits !== $readBits){
throw new \LogicException(get_class($this) . ": Exactly $stateBits bits of state data were provided, but $readBits were read");
}
}
/** /**
* @internal * @internal
*/ */
@ -219,12 +236,7 @@ class Block{
$stateBits = $this->getRequiredStateDataBits(); $stateBits = $this->getRequiredStateDataBits();
$reader = new RuntimeDataReader($typeBits + $stateBits, $data); $reader = new RuntimeDataReader($typeBits + $stateBits, $data);
$this->decodeTypeData($reader->readInt($typeBits)); $this->decodeTypeData($reader->readInt($typeBits));
$this->decodeStateData($reader->readInt($stateBits));
$this->describeState($reader);
$readBits = $reader->getOffset() - $typeBits;
if($stateBits !== $readBits){
throw new \LogicException(get_class($this) . ": Exactly $stateBits bits of state data were provided, but $readBits were read");
}
} }
/** /**
@ -243,6 +255,19 @@ class Block{
return $writer->getValue(); return $writer->getValue();
} }
private function computeStateData() : int{
$stateBits = $this->getRequiredStateDataBits();
$writer = new RuntimeDataWriter($stateBits);
$this->describeState($writer);
$writtenBits = $writer->getOffset();
if($stateBits !== $writtenBits){
throw new \LogicException(get_class($this) . ": Exactly $stateBits bits of state data were expected, but $writtenBits were written");
}
return $writer->getValue();
}
/** /**
* @internal * @internal
*/ */
@ -251,12 +276,7 @@ class Block{
$stateBits = $this->getRequiredStateDataBits(); $stateBits = $this->getRequiredStateDataBits();
$writer = new RuntimeDataWriter($typeBits + $stateBits); $writer = new RuntimeDataWriter($typeBits + $stateBits);
$writer->writeInt($typeBits, $this->computeTypeData()); $writer->writeInt($typeBits, $this->computeTypeData());
$writer->writeInt($stateBits, $this->computeStateData());
$this->describeState($writer);
$writtenBits = $writer->getOffset() - $typeBits;
if($stateBits !== $writtenBits){
throw new \LogicException(get_class($this) . ": Exactly $stateBits bits of state data were expected, but $writtenBits were written");
}
return $writer->getValue(); return $writer->getValue();
} }
@ -269,7 +289,7 @@ class Block{
* The method implementation must NOT use conditional logic to determine which properties are written. It must * The method implementation must NOT use conditional logic to determine which properties are written. It must
* always write the same properties in the same order, regardless of the current state of the block. * always write the same properties in the same order, regardless of the current state of the block.
*/ */
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
//NOOP //NOOP
} }

View File

@ -45,7 +45,7 @@ class Dirt extends Opaque{
parent::__construct($idInfo, $name, $typeInfo); parent::__construct($idInfo, $name, $typeInfo);
} }
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->dirtType($this->dirtType); $w->dirtType($this->dirtType);
} }

View File

@ -35,7 +35,7 @@ final class Froglight extends SimplePillar{
parent::__construct($idInfo, $name, $typeInfo); parent::__construct($idInfo, $name, $typeInfo);
} }
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->froglightType($this->froglightType); $w->froglightType($this->froglightType);
} }

View File

@ -34,7 +34,7 @@ final class Light extends Flowable{
private int $level = self::MAX_LIGHT_LEVEL; private int $level = self::MAX_LIGHT_LEVEL;
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->boundedInt(4, self::MIN_LIGHT_LEVEL, self::MAX_LIGHT_LEVEL, $this->level); $w->boundedInt(4, self::MIN_LIGHT_LEVEL, self::MAX_LIGHT_LEVEL, $this->level);
} }

View File

@ -52,7 +52,7 @@ class NetherVines extends Flowable{
parent::__construct($idInfo, $name, $typeInfo); parent::__construct($idInfo, $name, $typeInfo);
} }
public function describeState(RuntimeDataDescriber $w) : void{ protected function describeState(RuntimeDataDescriber $w) : void{
$w->boundedInt(5, 0, self::MAX_AGE, $this->age); $w->boundedInt(5, 0, self::MAX_AGE, $this->age);
} }

View File

@ -36,7 +36,7 @@ class RedMushroomBlock extends Opaque{
parent::__construct($idInfo, $name, $typeInfo); parent::__construct($idInfo, $name, $typeInfo);
} }
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
//these blocks always drop as all-cap, but may exist in other forms in the inventory (particularly creative), //these blocks always drop as all-cap, but may exist in other forms in the inventory (particularly creative),
//so this information needs to be kept in the type info //so this information needs to be kept in the type info
$w->mushroomBlockType($this->mushroomBlockType); $w->mushroomBlockType($this->mushroomBlockType);

View File

@ -151,18 +151,6 @@ class RuntimeBlockStateRegistry{
} }
} }
/**
* @internal
* Returns the default state of the block type associated with the given type ID.
*/
public function fromTypeId(int $typeId) : Block{
if(isset($this->typeIndex[$typeId])){
return clone $this->typeIndex[$typeId];
}
throw new \InvalidArgumentException("Block ID $typeId is not registered");
}
public function fromStateId(int $stateId) : Block{ public function fromStateId(int $stateId) : Block{
if($stateId < 0){ if($stateId < 0){
throw new \InvalidArgumentException("Block state ID cannot be negative"); throw new \InvalidArgumentException("Block state ID cannot be negative");
@ -178,22 +166,6 @@ class RuntimeBlockStateRegistry{
return $block; return $block;
} }
/**
* Returns whether a specified block state is already registered in the block factory.
*/
public function isRegistered(int $typeId) : bool{
$b = $this->typeIndex[$typeId] ?? null;
return $b !== null && !($b instanceof UnknownBlock);
}
/**
* @return Block[]
* @phpstan-return array<int, Block>
*/
public function getAllKnownTypes() : array{
return $this->typeIndex;
}
/** /**
* @return Block[] * @return Block[]
*/ */

View File

@ -49,7 +49,7 @@ class Skull extends Flowable{
parent::__construct($idInfo, $name, $typeInfo); parent::__construct($idInfo, $name, $typeInfo);
} }
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->skullType($this->skullType); $w->skullType($this->skullType);
} }

View File

@ -37,8 +37,8 @@ class Slab extends Transparent{
protected SlabType $slabType; protected SlabType $slabType;
public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){ public function __construct(BlockIdentifier $idInfo, string $name, BlockTypeInfo $typeInfo){
parent::__construct($idInfo, $name . " Slab", $typeInfo);
$this->slabType = SlabType::BOTTOM(); $this->slabType = SlabType::BOTTOM();
parent::__construct($idInfo, $name . " Slab", $typeInfo);
} }
protected function describeState(RuntimeDataDescriber $w) : void{ protected function describeState(RuntimeDataDescriber $w) : void{

View File

@ -28,7 +28,7 @@ use pocketmine\data\runtime\RuntimeDataDescriber;
class Sponge extends Opaque{ class Sponge extends Opaque{
protected bool $wet = false; protected bool $wet = false;
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->bool($this->wet); $w->bool($this->wet);
} }

View File

@ -45,7 +45,7 @@ class TNT extends Opaque{
protected bool $unstable = false; //TODO: Usage unclear, seems to be a weird hack in vanilla protected bool $unstable = false; //TODO: Usage unclear, seems to be a weird hack in vanilla
protected bool $worksUnderwater = false; protected bool $worksUnderwater = false;
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->bool($this->worksUnderwater); $w->bool($this->worksUnderwater);
} }

View File

@ -38,7 +38,7 @@ class UnknownBlock extends Transparent{
$this->stateData = $stateData; $this->stateData = $stateData;
} }
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
//use type instead of state, so we don't lose any information like colour //use type instead of state, so we don't lose any information like colour
//this might be an improperly registered plugin block //this might be an improperly registered plugin block
$w->int(Block::INTERNAL_STATE_DATA_BITS, $this->stateData); $w->int(Block::INTERNAL_STATE_DATA_BITS, $this->stateData);

View File

@ -38,7 +38,7 @@ class Wood extends Opaque{
private bool $stripped = false; private bool $stripped = false;
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->bool($this->stripped); $w->bool($this->stripped);
} }

View File

@ -31,7 +31,7 @@ trait ColoredTrait{
private $color; private $color;
/** @see Block::describeType() */ /** @see Block::describeType() */
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->dyeColor($this->color); $w->dyeColor($this->color);
} }

View File

@ -44,7 +44,7 @@ trait CopperTrait{
parent::__construct($identifier, $name, $typeInfo); parent::__construct($identifier, $name, $typeInfo);
} }
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->copperOxidation($this->oxidation); $w->copperOxidation($this->oxidation);
$w->bool($this->waxed); $w->bool($this->waxed);
} }

View File

@ -31,7 +31,7 @@ trait CoralTypeTrait{
protected bool $dead = false; protected bool $dead = false;
/** @see Block::describeType() */ /** @see Block::describeType() */
protected function describeType(RuntimeDataDescriber $w) : void{ public function describeType(RuntimeDataDescriber $w) : void{
$w->coralType($this->coralType); $w->coralType($this->coralType);
$w->bool($this->dead); $w->bool($this->dead);
} }

View File

@ -35,47 +35,29 @@ use pocketmine\data\runtime\RuntimeDataDescriber;
* just place wheat crops when used on the ground). * just place wheat crops when used on the ground).
*/ */
final class ItemBlock extends Item{ final class ItemBlock extends Item{
private int $blockTypeId; public function __construct(
private int $blockTypeData; private Block $block
){
private int $fuelTime;
private bool $fireProof;
private int $maxStackSize;
public function __construct(Block $block){
parent::__construct(ItemIdentifier::fromBlock($block), $block->getName()); parent::__construct(ItemIdentifier::fromBlock($block), $block->getName());
$this->blockTypeId = $block->getTypeId();
$this->blockTypeData = $block->computeTypeData();
$this->fuelTime = $block->getFuelTime();
$this->fireProof = $block->isFireProofAsItem();
$this->maxStackSize = $block->getMaxStackSize();
} }
protected function describeType(RuntimeDataDescriber $w) : void{ protected function describeType(RuntimeDataDescriber $w) : void{
$w->int(Block::INTERNAL_STATE_DATA_BITS, $this->blockTypeData); $this->block->describeType($w);
} }
public function getBlock(?int $clickedFace = null) : Block{ public function getBlock(?int $clickedFace = null) : Block{
//TODO: HACKY MESS, CLEAN IT UP return clone $this->block;
$factory = RuntimeBlockStateRegistry::getInstance();
if(!$factory->isRegistered($this->blockTypeId)){
return VanillaBlocks::AIR();
}
$blockType = $factory->fromTypeId($this->blockTypeId);
$blockType->decodeTypeData($this->blockTypeData);
return $blockType;
} }
public function getFuelTime() : int{ public function getFuelTime() : int{
return $this->fuelTime; return $this->block->getFuelTime();
} }
public function isFireProof() : bool{ public function isFireProof() : bool{
return $this->fireProof; return $this->block->isFireProofAsItem();
} }
public function getMaxStackSize() : int{ public function getMaxStackSize() : int{
return $this->maxStackSize; return $this->block->getMaxStackSize();
} }
} }