Nuke Block->meta, split into variant and state properties, lots of cleanup

This is a major change to the way block metadata is handled within the PM core. This separates variant metadata (which really ought to be part of the ID) from state metadata, and in a couple of cases flattens separate states of blocks together.

The result of this is that invalid variants can be much more easily detected, and additionally state handling is much cleaner since meta is only needed at the serialize layer instead of throughout the code.
This commit is contained in:
Dylan K. Taylor
2018-09-21 19:28:10 +01:00
parent a55ab54ddb
commit 56d9943b0d
178 changed files with 2012 additions and 2120 deletions

View File

@ -23,6 +23,8 @@ declare(strict_types=1);
namespace pocketmine\block;
use pocketmine\block\utils\Color;
use pocketmine\block\utils\WoodType;
use pocketmine\item\Item;
use pocketmine\level\Position;
@ -32,6 +34,8 @@ use pocketmine\level\Position;
class BlockFactory{
/** @var \SplFixedArray<Block> */
private static $fullList = null;
/** @var \SplFixedArray|\Closure[] */
private static $getInterceptors = null;
/** @var \SplFixedArray<int> */
public static $lightFilter = null;
@ -40,6 +44,9 @@ class BlockFactory{
/** @var \SplFixedArray<float> */
public static $blastResistance = null;
/** @var \SplFixedArray|int[] */
public static $stateMasks = null;
/** @var int[] */
public static $staticRuntimeIdMap = [];
@ -55,56 +62,138 @@ class BlockFactory{
*/
public static function init() : void{
self::$fullList = new \SplFixedArray(4096);
self::$getInterceptors = new \SplFixedArray(4096);
self::$lightFilter = \SplFixedArray::fromArray(array_fill(0, 256, 1));
self::$diffusesSkyLight = \SplFixedArray::fromArray(array_fill(0, 256, false));
self::$blastResistance = \SplFixedArray::fromArray(array_fill(0, 256, 0));
self::$stateMasks = \SplFixedArray::fromArray(array_fill(0, 256, 0));
self::registerBlock(new Air());
self::registerBlock(new Stone());
//TODO: give smooth stone its own class (different drops)
self::registerBlock(new Stone(Block::STONE, Stone::NORMAL, "Stone"));
self::registerBlock(new Stone(Block::STONE, Stone::GRANITE, "Granite"));
self::registerBlock(new Stone(Block::STONE, Stone::POLISHED_GRANITE, "Polished Granite"));
self::registerBlock(new Stone(Block::STONE, Stone::DIORITE, "Diorite"));
self::registerBlock(new Stone(Block::STONE, Stone::POLISHED_DIORITE, "Polished Diorite"));
self::registerBlock(new Stone(Block::STONE, Stone::ANDESITE, "Andesite"));
self::registerBlock(new Stone(Block::STONE, Stone::POLISHED_ANDESITE, "Polished Andesite"));
self::registerBlock(new Grass());
self::registerBlock(new Dirt());
//TODO: split these into separate classes
self::registerBlock(new Dirt(Block::DIRT, Dirt::NORMAL, "Dirt"));
self::registerBlock(new Dirt(Block::DIRT, Dirt::COARSE, "Coarse Dirt"));
self::registerBlock(new Cobblestone());
self::registerBlock(new Planks());
self::registerBlock(new Sapling());
foreach(WoodType::ALL as $type){
self::registerBlock(new Planks(Block::PLANKS, $type, WoodType::NAMES[$type] . " Planks"));
self::registerBlock(new Sapling(Block::SAPLING, $type, WoodType::NAMES[$type] . " Sapling"));
self::registerBlock(new WoodenFence(Block::FENCE, $type, WoodType::NAMES[$type] . " Fence"));
}
foreach(WoodType::ALL as $type){
//TODO: find a better way to deal with this split
self::registerBlock(new Wood($type >= 4 ? Block::WOOD2 : Block::WOOD, $type & 0x03, WoodType::NAMES[$type] . " Wood"));
self::registerBlock(new Leaves($type >= 4 ? Block::LEAVES2 : Block::LEAVES, $type & 0x03, $type, WoodType::NAMES[$type] . " Leaves"));
}
self::registerBlock(new Bedrock());
self::registerBlock(new Water());
self::registerBlock(new StillWater());
self::registerBlock(new Lava());
self::registerBlock(new StillLava());
self::registerBlock(new Sand());
self::registerBlock(new Sand(Block::SAND, 0, "Sand"));
self::registerBlock(new Sand(Block::SAND, 1, "Red Sand"));
self::registerBlock(new Gravel());
self::registerBlock(new GoldOre());
self::registerBlock(new IronOre());
self::registerBlock(new CoalOre());
self::registerBlock(new Wood());
self::registerBlock(new Leaves());
self::registerBlock(new Sponge());
self::registerBlock(new Glass());
self::registerBlock(new Glass(Block::GLASS, 0, "Glass"));
self::registerBlock(new LapisOre());
self::registerBlock(new Lapis());
//TODO: DISPENSER
self::registerBlock(new Sandstone());
static $sandstoneTypes = [
Sandstone::NORMAL => "",
Sandstone::CHISELED => "Chiseled ",
Sandstone::SMOOTH => "Smooth "
];
foreach($sandstoneTypes as $variant => $prefix){
self::registerBlock(new Sandstone(Block::SANDSTONE, $variant, $prefix . "Sandstone"));
self::registerBlock(new Sandstone(Block::RED_SANDSTONE, $variant, $prefix . "Red Sandstone"));
}
self::registerBlock(new NoteBlock());
self::registerBlock(new Bed());
self::registerBlock(new PoweredRail());
self::registerBlock(new DetectorRail());
//TODO: STICKY_PISTON
self::registerBlock(new Cobweb());
self::registerBlock(new TallGrass());
self::registerBlock(new TallGrass(Block::TALL_GRASS, 0, "Fern"));
self::registerBlock(new TallGrass(Block::TALL_GRASS, 1, "Tall Grass"));
self::registerBlock(new TallGrass(Block::TALL_GRASS, 2, "Fern"));
self::registerBlock(new TallGrass(Block::TALL_GRASS, 3, "Fern"));
self::registerBlock(new DeadBush());
//TODO: PISTON
//TODO: PISTONARMCOLLISION
self::registerBlock(new Wool());
foreach(Color::ALL as $color){
self::registerBlock(new Wool(Block::WOOL, $color, Color::NAMES[$color] . " Wool"));
self::registerBlock(new HardenedClay(Block::STAINED_CLAY, $color, Color::NAMES[$color] . " Stained Clay"));
self::registerBlock(new Glass(Block::STAINED_GLASS, $color, Color::NAMES[$color] . " Stained Glass"));
self::registerBlock(new GlassPane(Block::STAINED_GLASS_PANE, $color, Color::NAMES[$color] . " Stained Glass Pane"));
self::registerBlock(new Carpet(Block::CARPET, $color, Color::NAMES[$color] . " Carpet"));
self::registerBlock(new Concrete(Block::CONCRETE, $color, Color::NAMES[$color] . " Concrete"));
self::registerBlock(new ConcretePowder(Block::CONCRETE_POWDER, $color, Color::NAMES[$color] . " Concrete Powder"));
}
self::registerBlock(new Dandelion());
self::registerBlock(new Flower());
self::registerBlock(new Flower(Block::RED_FLOWER, Flower::TYPE_POPPY, "Poppy"));
self::registerBlock(new Flower(Block::RED_FLOWER, Flower::TYPE_BLUE_ORCHID, "Blue Orchid"));
self::registerBlock(new Flower(Block::RED_FLOWER, Flower::TYPE_ALLIUM, "Allium"));
self::registerBlock(new Flower(Block::RED_FLOWER, Flower::TYPE_AZURE_BLUET, "Azure Bluet"));
self::registerBlock(new Flower(Block::RED_FLOWER, Flower::TYPE_RED_TULIP, "Red Tulip"));
self::registerBlock(new Flower(Block::RED_FLOWER, Flower::TYPE_ORANGE_TULIP, "Orange Tulip"));
self::registerBlock(new Flower(Block::RED_FLOWER, Flower::TYPE_WHITE_TULIP, "White Tulip"));
self::registerBlock(new Flower(Block::RED_FLOWER, Flower::TYPE_PINK_TULIP, "Pink Tulip"));
self::registerBlock(new Flower(Block::RED_FLOWER, Flower::TYPE_OXEYE_DAISY, "Oxeye Daisy"));
self::registerBlock(new BrownMushroom());
self::registerBlock(new RedMushroom());
self::registerBlock(new Gold());
self::registerBlock(new Iron());
self::registerBlock(new DoubleStoneSlab());
self::registerBlock(new StoneSlab());
/** @var Slab[] $slabTypes */
$slabTypes = [
new StoneSlab(Block::STONE_SLAB, Block::DOUBLE_STONE_SLAB, 0, "Stone"),
new StoneSlab(Block::STONE_SLAB, Block::DOUBLE_STONE_SLAB, 1, "Sandstone"),
new StoneSlab(Block::STONE_SLAB, Block::DOUBLE_STONE_SLAB, 2, "Fake Wooden"),
new StoneSlab(Block::STONE_SLAB, Block::DOUBLE_STONE_SLAB, 3, "Cobblestone"),
new StoneSlab(Block::STONE_SLAB, Block::DOUBLE_STONE_SLAB, 4, "Brick"),
new StoneSlab(Block::STONE_SLAB, Block::DOUBLE_STONE_SLAB, 5, "Stone Brick"),
new StoneSlab(Block::STONE_SLAB, Block::DOUBLE_STONE_SLAB, 6, "Quartz"),
new StoneSlab(Block::STONE_SLAB, Block::DOUBLE_STONE_SLAB, 7, "Nether Brick"),
new StoneSlab(Block::STONE_SLAB2, Block::DOUBLE_STONE_SLAB2, 0, "Red Sandstone"),
new StoneSlab(Block::STONE_SLAB2, Block::DOUBLE_STONE_SLAB2, 1, "Purpur")
];
foreach(WoodType::ALL as $woodType){
$slabTypes[] = new WoodenSlab($woodType);
}
foreach($slabTypes as $type){
self::registerBlock($type);
self::registerBlock(new DoubleSlab($type->getDoubleSlabId(), $type->getId(), $type->getVariant()));
}
self::registerBlock(new Bricks());
self::registerBlock(new TNT());
self::registerBlock(new Bookshelf());
@ -121,8 +210,16 @@ class BlockFactory{
self::registerBlock(new CraftingTable());
self::registerBlock(new Wheat());
self::registerBlock(new Farmland());
self::registerBlock(new Furnace());
self::registerBlock(new BurningFurnace());
self::addGetInterceptor(Block::BURNING_FURNACE, 0, function() : Block{
$block = self::get(Block::FURNACE);
if($block instanceof Furnace){
$block->setLit();
}
return $block;
});
self::registerBlock(new SignPost());
self::registerBlock(new WoodenDoor(Block::OAK_DOOR_BLOCK, 0, "Oak Door", Item::OAK_DOOR));
self::registerBlock(new Ladder());
@ -134,9 +231,23 @@ class BlockFactory{
self::registerBlock(new IronDoor());
self::registerBlock(new WoodenPressurePlate());
self::registerBlock(new RedstoneOre());
self::registerBlock(new GlowingRedstoneOre());
self::registerBlock(new RedstoneTorchUnlit());
self::addGetInterceptor(Block::GLOWING_REDSTONE_ORE, 0, function() : Block{
$block = self::get(Block::REDSTONE_ORE);
if($block instanceof RedstoneOre){
$block->setLit();
}
return $block;
});
self::registerBlock(new RedstoneTorch());
self::addGetInterceptor(Block::UNLIT_REDSTONE_TORCH, 0, function() : Block{
$block = self::get(Block::REDSTONE_TORCH);
if($block instanceof RedstoneTorch){
$block->setLit(false); //default state is lit
}
return $block;
});
self::registerBlock(new StoneButton());
self::registerBlock(new SnowLayer());
self::registerBlock(new Ice());
@ -145,7 +256,7 @@ class BlockFactory{
self::registerBlock(new Clay());
self::registerBlock(new Sugarcane());
//TODO: JUKEBOX
self::registerBlock(new WoodenFence());
self::registerBlock(new Pumpkin());
self::registerBlock(new Netherrack());
self::registerBlock(new SoulSand());
@ -158,11 +269,16 @@ class BlockFactory{
//TODO: INVISIBLEBEDROCK
self::registerBlock(new Trapdoor());
//TODO: MONSTER_EGG
self::registerBlock(new StoneBricks());
self::registerBlock(new StoneBricks(Block::STONE_BRICKS, StoneBricks::NORMAL, "Stone Bricks"));
self::registerBlock(new StoneBricks(Block::STONE_BRICKS, StoneBricks::MOSSY, "Mossy Stone Bricks"));
self::registerBlock(new StoneBricks(Block::STONE_BRICKS, StoneBricks::CRACKED, "Cracked Stone Bricks"));
self::registerBlock(new StoneBricks(Block::STONE_BRICKS, StoneBricks::CHISELED, "Chiseled Stone Bricks"));
self::registerBlock(new BrownMushroomBlock());
self::registerBlock(new RedMushroomBlock());
self::registerBlock(new IronBars());
self::registerBlock(new GlassPane());
self::registerBlock(new GlassPane(Block::GLASS_PANE, 0, "Glass Pane"));
self::registerBlock(new Melon());
self::registerBlock(new PumpkinStem());
self::registerBlock(new MelonStem());
@ -184,7 +300,14 @@ class BlockFactory{
self::registerBlock(new EndStone());
//TODO: DRAGON_EGG
self::registerBlock(new RedstoneLamp());
self::registerBlock(new LitRedstoneLamp());
self::addGetInterceptor(Block::LIT_REDSTONE_LAMP, 0, function() : Block{
$block = self::get(Block::REDSTONE_LAMP);
if($block instanceof RedstoneLamp){
$block->setLit();
}
return $block;
});
//TODO: DROPPER
self::registerBlock(new ActivatorRail());
self::registerBlock(new CocoaBlock());
@ -199,50 +322,76 @@ class BlockFactory{
self::registerBlock(new WoodenStairs(Block::JUNGLE_STAIRS, 0, "Jungle Stairs"));
//TODO: COMMAND_BLOCK
//TODO: BEACON
self::registerBlock(new CobblestoneWall());
self::registerBlock(new CobblestoneWall(Block::COBBLESTONE_WALL, CobblestoneWall::NONE_MOSSY_WALL, "Cobblestone Wall"));
self::registerBlock(new CobblestoneWall(Block::COBBLESTONE_WALL, CobblestoneWall::MOSSY_WALL, "Mossy Cobblestone Wall"));
self::registerBlock(new FlowerPot());
self::registerBlock(new Carrot());
self::registerBlock(new Potato());
self::registerBlock(new WoodenButton());
self::registerBlock(new Skull());
self::registerBlock(new Anvil());
self::registerBlock(new Anvil(Block::ANVIL, Anvil::TYPE_NORMAL, "Anvil"));
self::registerBlock(new Anvil(Block::ANVIL, Anvil::TYPE_SLIGHTLY_DAMAGED, "Slightly Damaged Anvil"));
self::registerBlock(new Anvil(Block::ANVIL, Anvil::TYPE_VERY_DAMAGED, "Very Damaged Anvil"));
self::registerBlock(new TrappedChest());
self::registerBlock(new WeightedPressurePlateLight());
self::registerBlock(new WeightedPressurePlateHeavy());
//TODO: COMPARATOR_BLOCK
//TODO: POWERED_COMPARATOR
self::registerBlock(new DaylightSensor());
self::addGetInterceptor(Block::DAYLIGHT_SENSOR_INVERTED, 0, function() : Block{
$block = self::get(Block::DAYLIGHT_SENSOR);
if($block instanceof DaylightSensor){
$block->setInverted();
}
return $block;
});
self::registerBlock(new Redstone());
self::registerBlock(new NetherQuartzOre());
//TODO: HOPPER_BLOCK
self::registerBlock(new Quartz());
self::registerBlock(new Quartz(Block::QUARTZ_BLOCK, Quartz::NORMAL, "Quartz Block"));
self::registerBlock(new Quartz(Block::QUARTZ_BLOCK, Quartz::CHISELED, "Chiseled Quartz Block"));
self::registerBlock(new Quartz(Block::QUARTZ_BLOCK, Quartz::PILLAR, "Quartz Pillar"));
self::registerBlock(new Purpur(Block::PURPUR_BLOCK, Purpur::NORMAL, "Purpur Block"));
self::registerBlock(new Purpur(Block::PURPUR_BLOCK, Purpur::PILLAR, "Purpur Pillar"));
self::registerBlock(new QuartzStairs());
self::registerBlock(new DoubleWoodenSlab());
self::registerBlock(new WoodenSlab());
self::registerBlock(new StainedClay());
self::registerBlock(new StainedGlassPane());
self::registerBlock(new Leaves2());
self::registerBlock(new Wood2());
self::registerBlock(new WoodenStairs(Block::ACACIA_STAIRS, 0, "Acacia Stairs"));
self::registerBlock(new WoodenStairs(Block::DARK_OAK_STAIRS, 0, "Dark Oak Stairs"));
//TODO: SLIME
self::registerBlock(new IronTrapdoor());
self::registerBlock(new Prismarine());
self::registerBlock(new Prismarine(Block::PRISMARINE, Prismarine::NORMAL, "Prismarine"));
self::registerBlock(new Prismarine(Block::PRISMARINE, Prismarine::DARK, "Dark Prismarine"));
self::registerBlock(new Prismarine(Block::PRISMARINE, Prismarine::BRICKS, "Prismarine Bricks"));
self::registerBlock(new SeaLantern());
self::registerBlock(new HayBale());
self::registerBlock(new Carpet());
self::registerBlock(new HardenedClay());
self::registerBlock(new HardenedClay(Block::HARDENED_CLAY, 0, "Hardened Clay"));
self::registerBlock(new Coal());
self::registerBlock(new PackedIce());
self::registerBlock(new DoublePlant());
self::registerBlock(new DoublePlant(Block::DOUBLE_PLANT, 0, "Sunflower"));
self::registerBlock(new DoublePlant(Block::DOUBLE_PLANT, 1, "Lilac"));
//TODO: double tallgrass and large fern have different behaviour than the others, so they should get their own classes
self::registerBlock(new DoublePlant(Block::DOUBLE_PLANT, 2, "Double Tallgrass"));
self::registerBlock(new DoublePlant(Block::DOUBLE_PLANT, 3, "Large Fern"));
self::registerBlock(new DoublePlant(Block::DOUBLE_PLANT, 4, "Rose Bush"));
self::registerBlock(new DoublePlant(Block::DOUBLE_PLANT, 5, "Peony"));
self::registerBlock(new StandingBanner());
self::registerBlock(new WallBanner());
//TODO: DAYLIGHT_DETECTOR_INVERTED
self::registerBlock(new RedSandstone());
self::registerBlock(new RedSandstoneStairs());
self::registerBlock(new DoubleStoneSlab2());
self::registerBlock(new StoneSlab2());
self::registerBlock(new FenceGate(Block::SPRUCE_FENCE_GATE, 0, "Spruce Fence Gate"));
self::registerBlock(new FenceGate(Block::BIRCH_FENCE_GATE, 0, "Birch Fence Gate"));
self::registerBlock(new FenceGate(Block::JUNGLE_FENCE_GATE, 0, "Jungle Fence Gate"));
@ -259,7 +408,6 @@ class BlockFactory{
self::registerBlock(new GrassPath());
self::registerBlock(new ItemFrame());
//TODO: CHORUS_FLOWER
self::registerBlock(new Purpur());
self::registerBlock(new PurpurStairs());
@ -292,11 +440,8 @@ class BlockFactory{
self::registerBlock(new GlazedTerracotta(Block::GREEN_GLAZED_TERRACOTTA, 0, "Green Glazed Terracotta"));
self::registerBlock(new GlazedTerracotta(Block::RED_GLAZED_TERRACOTTA, 0, "Red Glazed Terracotta"));
self::registerBlock(new GlazedTerracotta(Block::BLACK_GLAZED_TERRACOTTA, 0, "Black Glazed Terracotta"));
self::registerBlock(new Concrete());
self::registerBlock(new ConcretePowder());
//TODO: CHORUS_PLANT
self::registerBlock(new StainedGlass());
self::registerBlock(new Podzol());
self::registerBlock(new Beetroot());
@ -327,20 +472,17 @@ class BlockFactory{
*/
public static function registerBlock(Block $block, bool $override = false) : void{
$id = $block->getId();
$variant = $block->getVariant();
if(!$override and self::isRegistered($id)){
if(!$override and self::isRegistered($id, $variant)){
throw new \RuntimeException("Trying to overwrite an already registered block");
}
for($meta = 0; $meta < 16; ++$meta){
$variant = clone $block;
$variant->setDamage($meta);
self::$fullList[($id << 4) | $meta] = $variant;
self::$fullList[($id << 4) | $variant] = clone $block;
if($variant === 0){
//TODO: allow these to differ for different variants
self::fillStaticArrays($id, $block);
}
self::$lightFilter[$id] = min(15, $block->getLightFilter() + 1); //opacity plus 1 standard light filter
self::$diffusesSkyLight[$id] = $block->diffusesSkyLight();
self::$blastResistance[$id] = $block->getBlastResistance();
}
/**
@ -357,16 +499,34 @@ class BlockFactory{
throw new \InvalidArgumentException("Block meta value $meta is out of bounds");
}
if(self::$stateMasks[$id] === null){
$variant = 0;
$state = $meta;
}else{
$variant = $meta & ~self::$stateMasks[$id];
$state = $meta & self::$stateMasks[$id];
}
$index = ($id << 4) | $variant;
/** @var Block|null $block */
$block = null;
try{
if(self::$fullList[($id << 4) | $meta] !== null){
$block = clone self::$fullList[($id << 4) | $meta];
}else{
$block = new UnknownBlock($id, $meta);
if(self::$getInterceptors[$index] !== null){
$block = (self::$getInterceptors[$index])();
}elseif(self::$fullList[$index] !== null){
$block = clone self::$fullList[$index];
}
}catch(\RuntimeException $e){
throw new \InvalidArgumentException("Block ID $id is out of bounds");
}
if($block !== null){
$block->readStateFromMeta($state);
}else{
$block = new UnknownBlock($id, $meta);
}
if($pos !== null){
$block->x = $pos->getFloorX();
$block->y = $pos->getFloorY();
@ -377,22 +537,32 @@ class BlockFactory{
return $block;
}
/**
* @internal
* @return \SplFixedArray
*/
public static function getBlockStatesArray() : \SplFixedArray{
return self::$fullList;
public static function addGetInterceptor(int $id, int $variant, \Closure $interceptor) : void{
$block = $interceptor();
if(!($block instanceof Block)){
throw new \InvalidArgumentException("Interceptor must return an instance of " . Block::class);
}
self::$getInterceptors[($id << 4) | $variant] = $interceptor;
self::fillStaticArrays($id, $block);
}
private static function fillStaticArrays(int $id, Block $block) : void{
self::$lightFilter[$id] = min(15, $block->getLightFilter() + 1); //opacity plus 1 standard light filter
self::$diffusesSkyLight[$id] = $block->diffusesSkyLight();
self::$blastResistance[$id] = $block->getBlastResistance();
self::$stateMasks[$id] = $block->getStateBitmask();
}
/**
* Returns whether a specified block ID is already registered in the block factory.
*
* @param int $id
* @param int $variant
*
* @return bool
*/
public static function isRegistered(int $id) : bool{
$b = self::$fullList[$id << 4];
public static function isRegistered(int $id, int $variant = 0) : bool{
$b = self::$fullList[($id << 4) | $variant];
return $b !== null and !($b instanceof UnknownBlock);
}