Allow static properties and state masks to vary based on variant

This commit is contained in:
Dylan K. Taylor 2018-10-06 17:33:28 +01:00
parent 9338061390
commit a430f7f4f7
8 changed files with 64 additions and 102 deletions

View File

@ -35,8 +35,6 @@ use pocketmine\level\Position;
class BlockFactory{ class BlockFactory{
/** @var \SplFixedArray<Block> */ /** @var \SplFixedArray<Block> */
private static $fullList = null; private static $fullList = null;
/** @var \SplFixedArray|\Closure[] */
private static $getInterceptors = null;
/** @var \SplFixedArray<int> */ /** @var \SplFixedArray<int> */
public static $lightFilter = null; public static $lightFilter = null;
@ -63,13 +61,12 @@ class BlockFactory{
*/ */
public static function init() : void{ public static function init() : void{
self::$fullList = new \SplFixedArray(8192); self::$fullList = new \SplFixedArray(8192);
self::$getInterceptors = new \SplFixedArray(8192);
self::$lightFilter = \SplFixedArray::fromArray(array_fill(0, 512, 1)); self::$lightFilter = \SplFixedArray::fromArray(array_fill(0, 8192, 1));
self::$diffusesSkyLight = \SplFixedArray::fromArray(array_fill(0, 512, false)); self::$diffusesSkyLight = \SplFixedArray::fromArray(array_fill(0, 8192, false));
self::$blastResistance = \SplFixedArray::fromArray(array_fill(0, 512, 0)); self::$blastResistance = \SplFixedArray::fromArray(array_fill(0, 8192, 0));
self::$stateMasks = new \SplFixedArray(512); self::$stateMasks = new \SplFixedArray(8192);
self::registerBlock(new Air()); self::registerBlock(new Air());
@ -212,13 +209,10 @@ class BlockFactory{
self::registerBlock(new Farmland()); self::registerBlock(new Farmland());
self::registerBlock(new Furnace()); self::registerBlock(new Furnace());
self::addGetInterceptor(Block::BURNING_FURNACE, 0, function() : Block{
$block = self::get(Block::FURNACE); $furnace = new Furnace();
if($block instanceof Furnace){ $furnace->setLit();
$block->setLit(); self::registerBlock($furnace); //flattening hack
}
return $block;
});
self::registerBlock(new SignPost()); self::registerBlock(new SignPost());
self::registerBlock(new WoodenDoor(Block::OAK_DOOR_BLOCK, 0, "Oak Door", Item::OAK_DOOR)); self::registerBlock(new WoodenDoor(Block::OAK_DOOR_BLOCK, 0, "Oak Door", Item::OAK_DOOR));
@ -231,22 +225,15 @@ class BlockFactory{
self::registerBlock(new IronDoor()); self::registerBlock(new IronDoor());
self::registerBlock(new WoodenPressurePlate()); self::registerBlock(new WoodenPressurePlate());
self::registerBlock(new RedstoneOre()); self::registerBlock(new RedstoneOre());
self::addGetInterceptor(Block::GLOWING_REDSTONE_ORE, 0, function() : Block{
$block = self::get(Block::REDSTONE_ORE); $litRedstone = new RedstoneOre();
if($block instanceof RedstoneOre){ $litRedstone->setLit();
$block->setLit(); self::registerBlock($litRedstone); //flattening hack
}
return $block;
});
self::registerBlock(new RedstoneTorch()); self::registerBlock(new RedstoneTorch());
self::addGetInterceptor(Block::UNLIT_REDSTONE_TORCH, 0, function() : Block{ $unlitRedstoneTorch = new RedstoneTorch();
$block = self::get(Block::REDSTONE_TORCH); $unlitRedstoneTorch->setLit(false);
if($block instanceof RedstoneTorch){ self::registerBlock($unlitRedstoneTorch); //flattening hack
$block->setLit(false); //default state is lit
}
return $block;
});
self::registerBlock(new StoneButton()); self::registerBlock(new StoneButton());
self::registerBlock(new SnowLayer()); self::registerBlock(new SnowLayer());
@ -300,13 +287,9 @@ class BlockFactory{
self::registerBlock(new EndStone()); self::registerBlock(new EndStone());
//TODO: DRAGON_EGG //TODO: DRAGON_EGG
self::registerBlock(new RedstoneLamp()); self::registerBlock(new RedstoneLamp());
self::addGetInterceptor(Block::LIT_REDSTONE_LAMP, 0, function() : Block{ $litLamp = new RedstoneLamp();
$block = self::get(Block::REDSTONE_LAMP); $litLamp->setLit();
if($block instanceof RedstoneLamp){ self::registerBlock($litLamp); //flattening hack
$block->setLit();
}
return $block;
});
//TODO: DROPPER //TODO: DROPPER
self::registerBlock(new ActivatorRail()); self::registerBlock(new ActivatorRail());
@ -342,13 +325,9 @@ class BlockFactory{
//TODO: COMPARATOR_BLOCK //TODO: COMPARATOR_BLOCK
//TODO: POWERED_COMPARATOR //TODO: POWERED_COMPARATOR
self::registerBlock(new DaylightSensor()); self::registerBlock(new DaylightSensor());
self::addGetInterceptor(Block::DAYLIGHT_SENSOR_INVERTED, 0, function() : Block{ $invertedSensor = new DaylightSensor();
$block = self::get(Block::DAYLIGHT_SENSOR); $invertedSensor->setInverted();
if($block instanceof DaylightSensor){ self::registerBlock($invertedSensor); //flattening hack
$block->setInverted();
}
return $block;
});
self::registerBlock(new Redstone()); self::registerBlock(new Redstone());
self::registerBlock(new NetherQuartzOre()); self::registerBlock(new NetherQuartzOre());
@ -479,18 +458,31 @@ class BlockFactory{
$id = $block->getId(); $id = $block->getId();
$variant = $block->getVariant(); $variant = $block->getVariant();
if(!$override and self::isRegistered($id, $variant)){
throw new \RuntimeException("Trying to overwrite an already registered block"); $stateMask = $block->getStateBitmask();
if(($variant & $stateMask) !== 0){
throw new \InvalidArgumentException("Block variant collides with state bitmask");
} }
if(self::$stateMasks[$id] !== null and self::$stateMasks[$id] !== $block->getStateBitmask()){ for($m = $variant; $m <= ($variant | $stateMask); ++$m){
throw new \InvalidArgumentException("Blocks with the same ID must have the same state bitmask"); if(($m & ~$stateMask) !== $variant){
continue;
} }
self::$fullList[($id << 4) | $variant] = clone $block; if(!$override and self::isRegistered($id, $m)){
if($variant === 0){ throw new \InvalidArgumentException("Block registration " . get_class($block) . " has states which conflict with other blocks");
//TODO: allow these to differ for different variants }
self::fillStaticArrays($id, $block);
$index = ($id << 4) | $m;
$v = clone $block;
$v->readStateFromMeta($m & $stateMask);
self::$fullList[$index] = $v;
self::$stateMasks[$index] = $stateMask;
self::$lightFilter[$index] = min(15, $v->getLightFilter() + 1); //opacity plus 1 standard light filter
self::$diffusesSkyLight[$index] = $v->diffusesSkyLight();
self::$blastResistance[$index] = $v->getBlastResistance();
} }
} }
@ -508,27 +500,18 @@ class BlockFactory{
throw new \InvalidArgumentException("Block meta value $meta is out of bounds"); throw new \InvalidArgumentException("Block meta value $meta is out of bounds");
} }
$stateMask = self::getStateMask($id);
$variant = $meta & ~$stateMask;
$state = $meta & $stateMask;
$index = ($id << 4) | $variant;
/** @var Block|null $block */ /** @var Block|null $block */
$block = null; $block = null;
try{ try{
if(self::$getInterceptors[$index] !== null){ $index = ($id << 4) | $meta;
$block = (self::$getInterceptors[$index])(); if(self::$fullList[$index] !== null){
}elseif(self::$fullList[$index] !== null){
$block = clone self::$fullList[$index]; $block = clone self::$fullList[$index];
} }
}catch(\RuntimeException $e){ }catch(\RuntimeException $e){
throw new \InvalidArgumentException("Block ID $id is out of bounds"); throw new \InvalidArgumentException("Block ID $id is out of bounds");
} }
if($block !== null){ if($block === null){
$block->readStateFromMeta($state);
}else{
$block = new UnknownBlock($id, $meta); $block = new UnknownBlock($id, $meta);
} }
@ -542,20 +525,8 @@ class BlockFactory{
return $block; return $block;
} }
public static function addGetInterceptor(int $id, int $variant, \Closure $interceptor) : void{ public static function fromFullBlock(int $fullState, ?Position $pos = null) : Block{
$block = $interceptor(); return self::get($fullState >> 4, $fullState & 0xf, $pos);
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();
} }
public static function getStateMask(int $id) : int{ public static function getStateMask(int $id) : int{
@ -563,15 +534,15 @@ class BlockFactory{
} }
/** /**
* Returns whether a specified block ID is already registered in the block factory. * Returns whether a specified block state is already registered in the block factory.
* *
* @param int $id * @param int $id
* @param int $variant * @param int $meta
* *
* @return bool * @return bool
*/ */
public static function isRegistered(int $id, int $variant = 0) : bool{ public static function isRegistered(int $id, int $meta = 0) : bool{
$b = self::$fullList[($id << 4) | $variant]; $b = self::$fullList[($id << 4) | $meta];
return $b !== null and !($b instanceof UnknownBlock); return $b !== null and !($b instanceof UnknownBlock);
} }

View File

@ -65,7 +65,7 @@ class Grass extends Solid{
public function onRandomTick() : void{ public function onRandomTick() : void{
$lightAbove = $this->level->getFullLightAt($this->x, $this->y + 1, $this->z); $lightAbove = $this->level->getFullLightAt($this->x, $this->y + 1, $this->z);
if($lightAbove < 4 and BlockFactory::$lightFilter[$this->level->getBlockIdAt($this->x, $this->y + 1, $this->z)] >= 3){ //2 plus 1 standard filter amount if($lightAbove < 4 and BlockFactory::$lightFilter[$this->level->getFullBlock($this->x, $this->y + 1, $this->z)] >= 3){ //2 plus 1 standard filter amount
//grass dies //grass dies
$ev = new BlockSpreadEvent($this, $this, BlockFactory::get(Block::DIRT)); $ev = new BlockSpreadEvent($this, $this, BlockFactory::get(Block::DIRT));
$ev->call(); $ev->call();
@ -82,7 +82,7 @@ class Grass extends Solid{
$this->level->getBlockIdAt($x, $y, $z) !== Block::DIRT or $this->level->getBlockIdAt($x, $y, $z) !== Block::DIRT or
$this->level->getBlockDataAt($x, $y, $z) === 1 or $this->level->getBlockDataAt($x, $y, $z) === 1 or
$this->level->getFullLightAt($x, $y + 1, $z) < 4 or $this->level->getFullLightAt($x, $y + 1, $z) < 4 or
BlockFactory::$lightFilter[$this->level->getBlockIdAt($x, $y + 1, $z)] >= 3 BlockFactory::$lightFilter[$this->level->getFullBlock($x, $y + 1, $z)] >= 3
){ ){
continue; continue;
} }

View File

@ -42,8 +42,4 @@ class Purpur extends Solid{
public function getBlastResistance() : float{ public function getBlastResistance() : float{
return 30; return 30;
} }
public function getStateBitmask() : int{
return 0b1100; //HACK: needs to be consistent for blocks with the same ID :(
}
} }

View File

@ -42,8 +42,4 @@ class Quartz extends Solid{
public function getToolHarvestLevel() : int{ public function getToolHarvestLevel() : int{
return TieredTool::TIER_WOODEN; return TieredTool::TIER_WOODEN;
} }
public function getStateBitmask() : int{
return 0b1100; //HACK: needs to be consistent for blocks with the same ID :(
}
} }

View File

@ -123,13 +123,13 @@ class Explosion{
continue; continue;
} }
$blockId = $this->subChunkHandler->currentSubChunk->getBlockId($vBlock->x & 0x0f, $vBlock->y & 0x0f, $vBlock->z & 0x0f); $state = $this->subChunkHandler->currentSubChunk->getFullBlock($vBlock->x & 0x0f, $vBlock->y & 0x0f, $vBlock->z & 0x0f);
if($blockId !== 0){ if($state !== 0){
$blastForce -= (BlockFactory::$blastResistance[$blockId] / 5 + 0.3) * $this->stepLen; $blastForce -= (BlockFactory::$blastResistance[$state] / 5 + 0.3) * $this->stepLen;
if($blastForce > 0){ if($blastForce > 0){
if(!isset($this->affectedBlocks[$index = Level::blockHash($vBlock->x, $vBlock->y, $vBlock->z)])){ if(!isset($this->affectedBlocks[$index = Level::blockHash($vBlock->x, $vBlock->y, $vBlock->z)])){
$this->affectedBlocks[$index] = BlockFactory::get($blockId, $this->subChunkHandler->currentSubChunk->getBlockData($vBlock->x & 0x0f, $vBlock->y & 0x0f, $vBlock->z & 0x0f), $vBlock); $this->affectedBlocks[$index] = BlockFactory::get($state >> 4, $state & 0xf, $vBlock);
} }
} }
} }

View File

@ -983,12 +983,11 @@ class Level implements ChunkManager, Metadatable{
$y = ($k >> 4) & 0x0f; $y = ($k >> 4) & 0x0f;
$z = ($k >> 8) & 0x0f; $z = ($k >> 8) & 0x0f;
$blockId = $subChunk->getBlockId($x, $y, $z); $state = $subChunk->getFullBlock($x, $y, $z);
$meta = $subChunk->getBlockData($x, $y, $z);
if($this->randomTickBlocks[($blockId << 4) | ($meta & ~BlockFactory::getStateMask($blockId))]){ if($this->randomTickBlocks[$state & ~BlockFactory::getStateMask($state >> 4)]){
/** @var Block $block */ /** @var Block $block */
$block = BlockFactory::get($blockId, $meta); $block = BlockFactory::fromFullBlock($state);
$block->x = $chunkX * 16 + $x; $block->x = $chunkX * 16 + $x;
$block->y = ($Y << 4) + $y; $block->y = ($Y << 4) + $y;

View File

@ -394,7 +394,7 @@ class Chunk{
public function recalculateHeightMapColumn(int $x, int $z) : int{ public function recalculateHeightMapColumn(int $x, int $z) : int{
$max = $this->getHighestBlockAt($x, $z); $max = $this->getHighestBlockAt($x, $z);
for($y = $max; $y >= 0; --$y){ for($y = $max; $y >= 0; --$y){
if(BlockFactory::$lightFilter[$id = $this->getBlockId($x, $y, $z)] > 1 or BlockFactory::$diffusesSkyLight[$id]){ if(BlockFactory::$lightFilter[$state = $this->getFullBlock($x, $y, $z)] > 1 or BlockFactory::$diffusesSkyLight[$state]){
break; break;
} }
} }
@ -426,7 +426,7 @@ class Chunk{
$light = 15; $light = 15;
for(; $y >= 0; --$y){ for(; $y >= 0; --$y){
if($light > 0){ if($light > 0){
$light -= BlockFactory::$lightFilter[$this->getBlockId($x, $y, $z)]; $light -= BlockFactory::$lightFilter[$this->getFullBlock($x, $y, $z)];
if($light <= 0){ if($light <= 0){
break; break;
} }

View File

@ -157,7 +157,7 @@ abstract class LightUpdate{
protected function computeSpreadLight(int $x, int $y, int $z, int $newAdjacentLevel){ protected function computeSpreadLight(int $x, int $y, int $z, int $newAdjacentLevel){
$current = $this->getLight($x, $y, $z); $current = $this->getLight($x, $y, $z);
$potentialLight = $newAdjacentLevel - BlockFactory::$lightFilter[$this->subChunkHandler->currentSubChunk->getBlockId($x & 0x0f, $y & 0x0f, $z & 0x0f)]; $potentialLight = $newAdjacentLevel - BlockFactory::$lightFilter[$this->subChunkHandler->currentSubChunk->getFullBlock($x & 0x0f, $y & 0x0f, $z & 0x0f)];
if($current < $potentialLight){ if($current < $potentialLight){
$this->setLight($x, $y, $z, $potentialLight); $this->setLight($x, $y, $z, $potentialLight);