Removed masks for Y coordinates, fixed bugs related to out-of-bounds coordinates, fixed #914 (#915)

This commit is contained in:
Dylan K. Taylor 2017-06-21 10:55:38 +01:00 committed by GitHub
parent 69ae37d191
commit 0f79b19fdc
6 changed files with 105 additions and 36 deletions

View File

@ -131,4 +131,22 @@ interface ChunkManager{
* @return int * @return int
*/ */
public function getSeed(); public function getSeed();
/**
* Returns the height of the world
* @return int
*/
public function getWorldHeight() : int;
/**
* Returns whether the specified coordinates are within the valid world boundaries, taking world format limitations
* into account.
*
* @param float $x
* @param float $y
* @param float $z
*
* @return bool
*/
public function isInWorld(float $x, float $y, float $z) : bool;
} }

View File

@ -89,7 +89,7 @@ class Explosion{
$vBlock->x = $pointerX >= $x ? $x : $x - 1; $vBlock->x = $pointerX >= $x ? $x : $x - 1;
$vBlock->y = $pointerY >= $y ? $y : $y - 1; $vBlock->y = $pointerY >= $y ? $y : $y - 1;
$vBlock->z = $pointerZ >= $z ? $z : $z - 1; $vBlock->z = $pointerZ >= $z ? $z : $z - 1;
if($vBlock->y < 0 or $vBlock->y >= Level::Y_MAX){ if(!$this->level->isInWorld($vBlock->x, $vBlock->y, $vBlock->z)){
break; break;
} }
$block = $this->level->getBlock($vBlock); $block = $this->level->getBlock($vBlock);

View File

@ -269,6 +269,9 @@ class Level implements ChunkManager, Metadatable{
} }
public static function blockHash(int $x, int $y, int $z){ public static function blockHash(int $x, int $y, int $z){
if($y < 0 or $y >= Level::Y_MAX){
throw new \InvalidArgumentException("Y coordinate $y is out of range!");
}
return (($x & 0xFFFFFFF) << 36) | (($y & Level::Y_MASK) << 28) | ($z & 0xFFFFFFF); return (($x & 0xFFFFFFF) << 36) | (($y & Level::Y_MASK) << 28) | ($z & 0xFFFFFFF);
} }
@ -1078,7 +1081,10 @@ class Level implements ChunkManager, Metadatable{
* @param int $delay * @param int $delay
*/ */
public function scheduleDelayedBlockUpdate(Vector3 $pos, int $delay){ public function scheduleDelayedBlockUpdate(Vector3 $pos, int $delay){
if(isset($this->scheduledBlockUpdateQueueIndex[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]) and $this->scheduledBlockUpdateQueueIndex[$index] <= $delay){ if(
!$this->isInWorld($pos->x, $pos->y, $pos->z) or
(isset($this->scheduledBlockUpdateQueueIndex[$index = Level::blockHash($pos->x, $pos->y, $pos->z)]) and $this->scheduledBlockUpdateQueueIndex[$index] <= $delay)
){
return; return;
} }
$this->scheduledBlockUpdateQueueIndex[$index] = $delay; $this->scheduledBlockUpdateQueueIndex[$index] = $delay;
@ -1094,12 +1100,12 @@ class Level implements ChunkManager, Metadatable{
public function scheduleNeighbourBlockUpdates(Vector3 $pos){ public function scheduleNeighbourBlockUpdates(Vector3 $pos){
$pos = $pos->floor(); $pos = $pos->floor();
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x + 1, $pos->y, $pos->z)); for($i = 0; $i <= 5; ++$i){
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x - 1, $pos->y, $pos->z)); $side = $pos->getSide($i);
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y + 1, $pos->z)); if($this->isInWorld($side->x, $side->y, $side->z)){
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y - 1, $pos->z)); $this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($side->x, $side->y, $side->z));
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y, $pos->z + 1)); }
$this->neighbourBlockUpdateQueue->enqueue(Level::blockHash($pos->x, $pos->y, $pos->z - 1)); }
} }
/** /**
@ -1298,23 +1304,39 @@ class Level implements ChunkManager, Metadatable{
return $this->getChunk($x >> 4, $z >> 4, false)->getFullBlock($x & 0x0f, $y, $z & 0x0f); return $this->getChunk($x >> 4, $z >> 4, false)->getFullBlock($x & 0x0f, $y, $z & 0x0f);
} }
public function isInWorld(float $x, float $y, float $z) : bool{
return (
$x <= INT32_MAX and $x >= INT32_MIN and
$y < $this->getWorldHeight() and $y >= 0 and
$z <= INT32_MAX and $z >= INT32_MIN
);
}
/** /**
* Gets the Block object on the Vector3 location * Gets the Block object at the Vector3 location
*
* Note for plugin developers: If you are using this method a lot (thousands of times for many positions for
* example), you may want to set addToCache to false to avoid using excessive amounts of memory.
* *
* @param Vector3 $pos * @param Vector3 $pos
* @param bool $cached * @param bool $cached Whether to use the block cache for getting the block (faster, but may be inaccurate)
* @param bool $addToCache Whether to cache the block object created by this method call.
* *
* @return Block * @return Block
*/ */
public function getBlock(Vector3 $pos, $cached = true) : Block{ public function getBlock(Vector3 $pos, bool $cached = true, bool $addToCache = true) : Block{
$pos = $pos->floor(); $pos = $pos->floor();
$index = Level::blockHash($pos->x, $pos->y, $pos->z);
if($cached and isset($this->blockCache[$index])){ $fullState = 0;
return $this->blockCache[$index]; $index = null;
}elseif($pos->y >= 0 and $pos->y < $this->provider->getWorldHeight() and isset($this->chunks[$chunkIndex = Level::chunkHash($pos->x >> 4, $pos->z >> 4)])){
$fullState = $this->chunks[$chunkIndex]->getFullBlock($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f); if($this->isInWorld($pos->x, $pos->y, $pos->z)){
}else{ $index = Level::blockHash($pos->x, $pos->y, $pos->z);
$fullState = 0; if($cached and isset($this->blockCache[$index])){
return $this->blockCache[$index];
}elseif(isset($this->chunks[$chunkIndex = Level::chunkHash($pos->x >> 4, $pos->z >> 4)])){
$fullState = $this->chunks[$chunkIndex]->getFullBlock($pos->x & 0x0f, $pos->y, $pos->z & 0x0f);
}
} }
$block = clone $this->blockStates[$fullState & 0xfff]; $block = clone $this->blockStates[$fullState & 0xfff];
@ -1324,7 +1346,11 @@ class Level implements ChunkManager, Metadatable{
$block->z = $pos->z; $block->z = $pos->z;
$block->level = $this; $block->level = $this;
return $this->blockCache[$index] = $block; if($addToCache and $index !== null){
$this->blockCache[$index] = $block;
}
return $block;
} }
public function updateAllLight(Vector3 $pos){ public function updateAllLight(Vector3 $pos){
@ -1446,13 +1472,13 @@ class Level implements ChunkManager, Metadatable{
*/ */
public function setBlock(Vector3 $pos, Block $block, bool $direct = false, bool $update = true) : bool{ public function setBlock(Vector3 $pos, Block $block, bool $direct = false, bool $update = true) : bool{
$pos = $pos->floor(); $pos = $pos->floor();
if($pos->y < 0 or $pos->y >= $this->provider->getWorldHeight()){ if(!$this->isInWorld($pos->x, $pos->y, $pos->z)){
return false; return false;
} }
$this->timings->setBlock->startTiming(); $this->timings->setBlock->startTiming();
if($this->getChunk($pos->x >> 4, $pos->z >> 4, true)->setBlock($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f, $block->getId(), $block->getDamage())){ if($this->getChunk($pos->x >> 4, $pos->z >> 4, true)->setBlock($pos->x & 0x0f, $pos->y, $pos->z & 0x0f, $block->getId(), $block->getDamage())){
if(!($pos instanceof Position)){ if(!($pos instanceof Position)){
$pos = $this->temporalPosition->setComponents($pos->x, $pos->y, $pos->z); $pos = $this->temporalPosition->setComponents($pos->x, $pos->y, $pos->z);
} }
@ -1942,7 +1968,7 @@ class Level implements ChunkManager, Metadatable{
$chunk = $this->getChunk($pos->x >> 4, $pos->z >> 4, false); $chunk = $this->getChunk($pos->x >> 4, $pos->z >> 4, false);
if($chunk !== null){ if($chunk !== null){
return $chunk->getTile($pos->x & 0x0f, $pos->y & Level::Y_MASK, $pos->z & 0x0f); return $chunk->getTile($pos->x & 0x0f, $pos->y, $pos->z & 0x0f);
} }
return null; return null;
@ -1982,7 +2008,7 @@ class Level implements ChunkManager, Metadatable{
* @return int 0-255 * @return int 0-255
*/ */
public function getBlockIdAt(int $x, int $y, int $z) : int{ public function getBlockIdAt(int $x, int $y, int $z) : int{
return $this->getChunk($x >> 4, $z >> 4, true)->getBlockId($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); return $this->getChunk($x >> 4, $z >> 4, true)->getBlockId($x & 0x0f, $y, $z & 0x0f);
} }
/** /**
@ -1995,7 +2021,7 @@ class Level implements ChunkManager, Metadatable{
*/ */
public function setBlockIdAt(int $x, int $y, int $z, int $id){ public function setBlockIdAt(int $x, int $y, int $z, int $id){
unset($this->blockCache[Level::blockHash($x, $y, $z)]); unset($this->blockCache[Level::blockHash($x, $y, $z)]);
$this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $id & 0xff); $this->getChunk($x >> 4, $z >> 4, true)->setBlockId($x & 0x0f, $y, $z & 0x0f, $id & 0xff);
if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){ if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){
$this->changedBlocks[$index] = []; $this->changedBlocks[$index] = [];
@ -2016,7 +2042,7 @@ class Level implements ChunkManager, Metadatable{
* @return int 16-bit * @return int 16-bit
*/ */
public function getBlockExtraDataAt(int $x, int $y, int $z) : int{ public function getBlockExtraDataAt(int $x, int $y, int $z) : int{
return $this->getChunk($x >> 4, $z >> 4, true)->getBlockExtraData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); return $this->getChunk($x >> 4, $z >> 4, true)->getBlockExtraData($x & 0x0f, $y, $z & 0x0f);
} }
/** /**
@ -2029,7 +2055,7 @@ class Level implements ChunkManager, Metadatable{
* @param int $data * @param int $data
*/ */
public function setBlockExtraDataAt(int $x, int $y, int $z, int $id, int $data){ public function setBlockExtraDataAt(int $x, int $y, int $z, int $id, int $data){
$this->getChunk($x >> 4, $z >> 4, true)->setBlockExtraData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, ($data << 8) | $id); $this->getChunk($x >> 4, $z >> 4, true)->setBlockExtraData($x & 0x0f, $y, $z & 0x0f, ($data << 8) | $id);
$this->sendBlockExtraData($x, $y, $z, $id, $data); $this->sendBlockExtraData($x, $y, $z, $id, $data);
} }
@ -2044,7 +2070,7 @@ class Level implements ChunkManager, Metadatable{
* @return int 0-15 * @return int 0-15
*/ */
public function getBlockDataAt(int $x, int $y, int $z) : int{ public function getBlockDataAt(int $x, int $y, int $z) : int{
return $this->getChunk($x >> 4, $z >> 4, true)->getBlockData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); return $this->getChunk($x >> 4, $z >> 4, true)->getBlockData($x & 0x0f, $y, $z & 0x0f);
} }
/** /**
@ -2057,7 +2083,7 @@ class Level implements ChunkManager, Metadatable{
*/ */
public function setBlockDataAt(int $x, int $y, int $z, int $data){ public function setBlockDataAt(int $x, int $y, int $z, int $data){
unset($this->blockCache[Level::blockHash($x, $y, $z)]); unset($this->blockCache[Level::blockHash($x, $y, $z)]);
$this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $data & 0x0f); $this->getChunk($x >> 4, $z >> 4, true)->setBlockData($x & 0x0f, $y, $z & 0x0f, $data & 0x0f);
if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){ if(!isset($this->changedBlocks[$index = Level::chunkHash($x >> 4, $z >> 4)])){
$this->changedBlocks[$index] = []; $this->changedBlocks[$index] = [];
@ -2078,7 +2104,7 @@ class Level implements ChunkManager, Metadatable{
* @return int 0-15 * @return int 0-15
*/ */
public function getBlockSkyLightAt(int $x, int $y, int $z) : int{ public function getBlockSkyLightAt(int $x, int $y, int $z) : int{
return $this->getChunk($x >> 4, $z >> 4, true)->getBlockSkyLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); return $this->getChunk($x >> 4, $z >> 4, true)->getBlockSkyLight($x & 0x0f, $y, $z & 0x0f);
} }
/** /**
@ -2090,7 +2116,7 @@ class Level implements ChunkManager, Metadatable{
* @param int $level 0-15 * @param int $level 0-15
*/ */
public function setBlockSkyLightAt(int $x, int $y, int $z, int $level){ public function setBlockSkyLightAt(int $x, int $y, int $z, int $level){
$this->getChunk($x >> 4, $z >> 4, true)->setBlockSkyLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $level & 0x0f); $this->getChunk($x >> 4, $z >> 4, true)->setBlockSkyLight($x & 0x0f, $y, $z & 0x0f, $level & 0x0f);
} }
/** /**
@ -2103,7 +2129,7 @@ class Level implements ChunkManager, Metadatable{
* @return int 0-15 * @return int 0-15
*/ */
public function getBlockLightAt(int $x, int $y, int $z) : int{ public function getBlockLightAt(int $x, int $y, int $z) : int{
return $this->getChunk($x >> 4, $z >> 4, true)->getBlockLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f); return $this->getChunk($x >> 4, $z >> 4, true)->getBlockLight($x & 0x0f, $y, $z & 0x0f);
} }
/** /**
@ -2115,7 +2141,7 @@ class Level implements ChunkManager, Metadatable{
* @param int $level 0-15 * @param int $level 0-15
*/ */
public function setBlockLightAt(int $x, int $y, int $z, int $level){ public function setBlockLightAt(int $x, int $y, int $z, int $level){
$this->getChunk($x >> 4, $z >> 4, true)->setBlockLight($x & 0x0f, $y & Level::Y_MASK, $z & 0x0f, $level & 0x0f); $this->getChunk($x >> 4, $z >> 4, true)->setBlockLight($x & 0x0f, $y, $z & 0x0f, $level & 0x0f);
} }
/** /**
@ -2763,6 +2789,10 @@ class Level implements ChunkManager, Metadatable{
$this->provider->setSeed($seed); $this->provider->setSeed($seed);
} }
public function getWorldHeight() : int{
return $this->provider->getWorldHeight();
}
public function populateChunk(int $x, int $z, bool $force = false) : bool{ public function populateChunk(int $x, int $z, bool $force = false) : bool{
if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)]) or (count($this->chunkPopulationQueue) >= $this->chunkPopulationQueueSize and !$force)){ if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)]) or (count($this->chunkPopulationQueue) >= $this->chunkPopulationQueueSize and !$force)){

View File

@ -31,9 +31,11 @@ class SimpleChunkManager implements ChunkManager{
protected $chunks = []; protected $chunks = [];
protected $seed; protected $seed;
protected $worldHeight;
public function __construct($seed){ public function __construct($seed, int $worldHeight = Level::Y_MAX){
$this->seed = $seed; $this->seed = $seed;
$this->worldHeight = $worldHeight;
} }
/** /**
@ -159,4 +161,16 @@ class SimpleChunkManager implements ChunkManager{
public function getSeed(){ public function getSeed(){
return $this->seed; return $this->seed;
} }
public function getWorldHeight() : int{
return $this->worldHeight;
}
public function isInWorld(float $x, float $y, float $z) : bool{
return (
$x <= INT32_MAX and $x >= INT32_MIN and
$y < $this->worldHeight and $y >= 0 and
$z <= INT32_MAX and $z >= INT32_MIN
);
}
} }

View File

@ -36,18 +36,20 @@ class GeneratorRegisterTask extends AsyncTask{
public $settings; public $settings;
public $seed; public $seed;
public $levelId; public $levelId;
public $worldHeight = Level::Y_MAX;
public function __construct(Level $level, Generator $generator){ public function __construct(Level $level, Generator $generator){
$this->generator = get_class($generator); $this->generator = get_class($generator);
$this->settings = serialize($generator->getSettings()); $this->settings = serialize($generator->getSettings());
$this->seed = $level->getSeed(); $this->seed = $level->getSeed();
$this->levelId = $level->getId(); $this->levelId = $level->getId();
$this->worldHeight = $level->getWorldHeight();
} }
public function onRun(){ public function onRun(){
Block::init(); Block::init();
Biome::init(); Biome::init();
$manager = new SimpleChunkManager($this->seed); $manager = new SimpleChunkManager($this->seed, $this->worldHeight);
$this->saveToThreadStore("generation.level{$this->levelId}.manager", $manager); $this->saveToThreadStore("generation.level{$this->levelId}.manager", $manager);
/** @var Generator $generator */ /** @var Generator $generator */
$generator = $this->generator; $generator = $this->generator;

View File

@ -62,9 +62,14 @@ abstract class LightUpdate{
abstract protected function setLight(int $x, int $y, int $z, int $level); abstract protected function setLight(int $x, int $y, int $z, int $level);
public function setAndUpdateLight(int $x, int $y, int $z, int $newLevel){ public function setAndUpdateLight(int $x, int $y, int $z, int $newLevel){
if(!$this->level->isInWorld($x, $y, $z)){
throw new \InvalidArgumentException("Coordinates x=$x, y=$y, z=$z are out of range");
}
if(isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)]) or isset($this->removalVisited[$index])){ if(isset($this->spreadVisited[$index = Level::blockHash($x, $y, $z)]) or isset($this->removalVisited[$index])){
throw new \InvalidArgumentException("Already have a visit ready for this block"); throw new \InvalidArgumentException("Already have a visit ready for this block");
} }
$oldLevel = $this->getLight($x, $y, $z); $oldLevel = $this->getLight($x, $y, $z);
if($oldLevel !== $newLevel){ if($oldLevel !== $newLevel){
@ -93,7 +98,7 @@ abstract class LightUpdate{
]; ];
foreach($points as list($cx, $cy, $cz)){ foreach($points as list($cx, $cy, $cz)){
if($cy < 0){ if(!$this->level->isInWorld($cx, $cy, $cz)){
continue; continue;
} }
$this->computeRemoveLight($cx, $cy, $cz, $oldAdjacentLight); $this->computeRemoveLight($cx, $cy, $cz, $oldAdjacentLight);
@ -118,7 +123,7 @@ abstract class LightUpdate{
]; ];
foreach($points as list($cx, $cy, $cz)){ foreach($points as list($cx, $cy, $cz)){
if($cy < 0){ if(!$this->level->isInWorld($cx, $cy, $cz)){
continue; continue;
} }
$this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight); $this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight);