diff --git a/src/pocketmine/block/ActivatorRail.php b/src/pocketmine/block/ActivatorRail.php index ec7bf85d4..88d4a9f7f 100644 --- a/src/pocketmine/block/ActivatorRail.php +++ b/src/pocketmine/block/ActivatorRail.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -class ActivatorRail extends Rail{ +class ActivatorRail extends RedstoneRail{ protected $id = self::ACTIVATOR_RAIL; diff --git a/src/pocketmine/block/BaseRail.php b/src/pocketmine/block/BaseRail.php new file mode 100644 index 000000000..69f555d3c --- /dev/null +++ b/src/pocketmine/block/BaseRail.php @@ -0,0 +1,267 @@ + Facing::NORTH, + self::ASCENDING_EAST => Facing::EAST, + self::ASCENDING_SOUTH => Facing::SOUTH, + self::ASCENDING_WEST => Facing::WEST + ]; + + protected const FLAG_ASCEND = 1 << 24; //used to indicate direction-up + + protected const CONNECTIONS = [ + //straights + self::STRAIGHT_NORTH_SOUTH => [ + Facing::NORTH, + Facing::SOUTH + ], + self::STRAIGHT_EAST_WEST => [ + Facing::EAST, + Facing::WEST + ], + + //ascending + self::ASCENDING_EAST => [ + Facing::WEST, + Facing::EAST | self::FLAG_ASCEND + ], + self::ASCENDING_WEST => [ + Facing::EAST, + Facing::WEST | self::FLAG_ASCEND + ], + self::ASCENDING_NORTH => [ + Facing::SOUTH, + Facing::NORTH | self::FLAG_ASCEND + ], + self::ASCENDING_SOUTH => [ + Facing::NORTH, + Facing::SOUTH | self::FLAG_ASCEND + ] + ]; + + public function __construct(int $meta = 0){ + $this->meta = $meta; + } + + public function getHardness() : float{ + return 0.7; + } + + public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{ + if(!$blockReplace->getSide(Facing::DOWN)->isTransparent() and $this->getLevel()->setBlock($blockReplace, $this, true, true)){ + $this->tryReconnect(); + return true; + } + + return false; + } + + protected static function searchState(array $connections, array $lookup) : int{ + $meta = array_search($connections, $lookup, true); + if($meta === false){ + $meta = array_search(array_reverse($connections), $lookup, true); + } + if($meta === false){ + throw new \InvalidArgumentException("No meta value matches connections " . implode(", ", array_map('dechex', $connections))); + } + + return $meta; + } + + /** + * Returns a meta value for the rail with the given connections. + * + * @param array $connections + * + * @return int + * + * @throws \InvalidArgumentException if no state matches the given connections + */ + protected function getMetaForState(array $connections) : int{ + return self::searchState($connections, self::CONNECTIONS); + } + + /** + * Returns the connection directions of this rail (depending on the current block state) + * + * @return int[] + */ + abstract protected function getConnectionsForState() : array; + + /** + * Returns all the directions this rail is already connected in. + * + * @return int[] + */ + private function getConnectedDirections() : array{ + /** @var int[] $connections */ + $connections = []; + + /** @var int $connection */ + foreach($this->getConnectionsForState() as $connection){ + $other = $this->getSide($connection & ~self::FLAG_ASCEND); + $otherConnection = Facing::opposite($connection & ~self::FLAG_ASCEND); + + if(($connection & self::FLAG_ASCEND) !== 0){ + $other = $other->getSide(Facing::UP); + + }elseif(!($other instanceof BaseRail)){ //check for rail sloping up to meet this one + $other = $other->getSide(Facing::DOWN); + $otherConnection |= self::FLAG_ASCEND; + } + + if( + $other instanceof BaseRail and + in_array($otherConnection, $other->getConnectionsForState(), true) + ){ + $connections[] = $connection; + } + } + + return $connections; + } + + private function getPossibleConnectionDirections(array $constraints) : array{ + switch(count($constraints)){ + case 0: + //No constraints, can connect in any direction + $possible = [ + Facing::NORTH => true, + Facing::SOUTH => true, + Facing::WEST => true, + Facing::EAST => true + ]; + foreach($possible as $p => $_){ + $possible[$p | self::FLAG_ASCEND] = true; + } + + return $possible; + case 1: + return $this->getPossibleConnectionDirectionsOneConstraint(array_shift($constraints)); + case 2: + return []; + default: + throw new \InvalidArgumentException("Expected at most 2 constraints, got " . count($constraints)); + } + } + + protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{ + $opposite = Facing::opposite($constraint & ~self::FLAG_ASCEND); + + $possible = [$opposite => true]; + + if(($constraint & self::FLAG_ASCEND) === 0){ + //We can slope the other way if this connection isn't already a slope + $possible[$opposite | self::FLAG_ASCEND] = true; + } + + return $possible; + } + + private function tryReconnect() : void{ + $thisConnections = $this->getConnectedDirections(); + $changed = false; + + do{ + $possible = $this->getPossibleConnectionDirections($thisConnections); + $continue = false; + + foreach($possible as $thisSide => $_){ + $otherSide = Facing::opposite($thisSide & ~self::FLAG_ASCEND); + + $other = $this->getSide($thisSide & ~self::FLAG_ASCEND); + + if(($thisSide & self::FLAG_ASCEND) !== 0){ + $other = $other->getSide(Facing::UP); + + }elseif(!($other instanceof BaseRail)){ //check if other rails can slope up to meet this one + $other = $other->getSide(Facing::DOWN); + $otherSide |= self::FLAG_ASCEND; + } + + if(!($other instanceof BaseRail) or count($otherConnections = $other->getConnectedDirections()) >= 2){ + //we can only connect to a rail that has less than 2 connections + continue; + } + + $otherPossible = $other->getPossibleConnectionDirections($otherConnections); + + if(isset($otherPossible[$otherSide])){ + $otherConnections[] = $otherSide; + $other->updateState($otherConnections); + + $changed = true; + $thisConnections[] = $thisSide; + $continue = count($thisConnections) < 2; + + break; //force recomputing possible directions, since this connection could invalidate others + } + } + }while($continue); + + if($changed){ + $this->updateState($thisConnections); + } + } + + private function updateState(array $connections) : void{ + if(count($connections) === 1){ + $connections[] = Facing::opposite($connections[0] & ~self::FLAG_ASCEND); + }elseif(count($connections) !== 2){ + throw new \InvalidArgumentException("Expected exactly 2 connections, got " . count($connections)); + } + + $this->meta = $this->getMetaForState($connections); + $this->level->setBlock($this, $this, false, false); //avoid recursion + } + + public function onNearbyBlockChange() : void{ + if($this->getSide(Facing::DOWN)->isTransparent() or ( + isset(self::ASCENDING_SIDES[$this->meta & 0x07]) and + $this->getSide(self::ASCENDING_SIDES[$this->meta & 0x07])->isTransparent() + )){ + $this->getLevel()->useBreakOn($this); + } + } + + public function getVariantBitmask() : int{ + return 0; + } +} diff --git a/src/pocketmine/block/DetectorRail.php b/src/pocketmine/block/DetectorRail.php index 367ed14c0..a4eddc910 100644 --- a/src/pocketmine/block/DetectorRail.php +++ b/src/pocketmine/block/DetectorRail.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -class DetectorRail extends Rail{ +class DetectorRail extends RedstoneRail{ protected $id = self::DETECTOR_RAIL; diff --git a/src/pocketmine/block/PoweredRail.php b/src/pocketmine/block/PoweredRail.php index fe559a80a..ed7b1a00b 100644 --- a/src/pocketmine/block/PoweredRail.php +++ b/src/pocketmine/block/PoweredRail.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -class PoweredRail extends Rail{ +class PoweredRail extends RedstoneRail{ protected $id = self::POWERED_RAIL; public function getName() : string{ diff --git a/src/pocketmine/block/Rail.php b/src/pocketmine/block/Rail.php index 7e5e36f97..643e61ff0 100644 --- a/src/pocketmine/block/Rail.php +++ b/src/pocketmine/block/Rail.php @@ -23,55 +23,71 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\item\Item; use pocketmine\math\Facing; -use pocketmine\math\Vector3; -use pocketmine\Player; -class Rail extends Flowable{ +class Rail extends BaseRail{ - public const STRAIGHT_NORTH_SOUTH = 0; - public const STRAIGHT_EAST_WEST = 1; - public const ASCENDING_EAST = 2; - public const ASCENDING_WEST = 3; - public const ASCENDING_NORTH = 4; - public const ASCENDING_SOUTH = 5; + /* extended meta values for regular rails, to allow curving */ public const CURVE_SOUTHEAST = 6; public const CURVE_SOUTHWEST = 7; public const CURVE_NORTHWEST = 8; public const CURVE_NORTHEAST = 9; - protected $id = self::RAIL; + private const CURVE_CONNECTIONS = [ + self::CURVE_SOUTHEAST => [ + Facing::SOUTH, + Facing::EAST + ], + self::CURVE_SOUTHWEST => [ + Facing::SOUTH, + Facing::WEST + ], + self::CURVE_NORTHWEST => [ + Facing::NORTH, + Facing::WEST + ], + self::CURVE_NORTHEAST => [ + Facing::NORTH, + Facing::EAST + ] + ]; - public function __construct(int $meta = 0){ - $this->meta = $meta; - } + protected $id = self::RAIL; public function getName() : string{ return "Rail"; } - public function getHardness() : float{ - return 0.7; - } - - public function place(Item $item, Block $blockReplace, Block $blockClicked, int $face, Vector3 $clickVector, Player $player = null) : bool{ - if(!$blockReplace->getSide(Facing::DOWN)->isTransparent()){ - return $this->getLevel()->setBlock($blockReplace, $this, true, true); - } - - return false; - } - - public function onNearbyBlockChange() : void{ - if($this->getSide(Facing::DOWN)->isTransparent()){ - $this->getLevel()->useBreakOn($this); - }else{ - //TODO: Update rail connectivity + protected function getMetaForState(array $connections) : int{ + try{ + return self::searchState($connections, self::CURVE_CONNECTIONS); + }catch(\InvalidArgumentException $e){ + return parent::getMetaForState($connections); } } - public function getVariantBitmask() : int{ - return 0; + protected function getConnectionsForState() : array{ + return self::CURVE_CONNECTIONS[$this->meta] ?? self::CONNECTIONS[$this->meta]; + } + + protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{ + static $horizontal = [ + Facing::NORTH, + Facing::SOUTH, + Facing::WEST, + Facing::EAST + ]; + + $possible = parent::getPossibleConnectionDirectionsOneConstraint($constraint); + + if(($constraint & self::FLAG_ASCEND) === 0){ + foreach($horizontal as $d){ + if($constraint !== $d){ + $possible[$d] = true; + } + } + } + + return $possible; } } diff --git a/src/pocketmine/block/RedstoneRail.php b/src/pocketmine/block/RedstoneRail.php new file mode 100644 index 000000000..7d3b9cd4c --- /dev/null +++ b/src/pocketmine/block/RedstoneRail.php @@ -0,0 +1,32 @@ +meta & ~self::FLAG_POWERED]; + } +} diff --git a/src/pocketmine/level/light/LightUpdate.php b/src/pocketmine/level/light/LightUpdate.php index 61778de0a..2ee6d3479 100644 --- a/src/pocketmine/level/light/LightUpdate.php +++ b/src/pocketmine/level/light/LightUpdate.php @@ -34,6 +34,9 @@ abstract class LightUpdate{ /** @var ChunkManager */ protected $level; + /** @var int[] blockhash => new light level */ + protected $updateNodes = []; + /** @var \SplQueue */ protected $spreadQueue; /** @var bool[] */ @@ -54,44 +57,36 @@ abstract class LightUpdate{ $this->subChunkHandler = new SubChunkIteratorManager($this->level); } - public function addSpreadNode(int $x, int $y, int $z){ - $this->spreadQueue->enqueue([$x, $y, $z]); - } - - public function addRemoveNode(int $x, int $y, int $z, int $oldLight){ - $this->spreadQueue->enqueue([$x, $y, $z, $oldLight]); - } - abstract protected function getLight(int $x, int $y, int $z) : int; abstract protected function setLight(int $x, int $y, int $z, int $level); 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"); - } + $this->updateNodes[Level::blockHash($x, $y, $z)] = [$x, $y, $z, $newLevel]; + } - 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"); - } + private function prepareNodes() : void{ + foreach($this->updateNodes as $blockHash => [$x, $y, $z, $newLevel]){ + if($this->subChunkHandler->moveTo($x, $y, $z)){ + $oldLevel = $this->getLight($x, $y, $z); - if($this->subChunkHandler->moveTo($x, $y, $z)){ - $oldLevel = $this->getLight($x, $y, $z); - - if($oldLevel !== $newLevel){ - $this->setLight($x, $y, $z, $newLevel); - if($oldLevel < $newLevel){ //light increased - $this->spreadVisited[$index] = true; - $this->spreadQueue->enqueue([$x, $y, $z]); - }else{ //light removed - $this->removalVisited[$index] = true; - $this->removalQueue->enqueue([$x, $y, $z, $oldLevel]); + if($oldLevel !== $newLevel){ + $this->setLight($x, $y, $z, $newLevel); + if($oldLevel < $newLevel){ //light increased + $this->spreadVisited[$blockHash] = true; + $this->spreadQueue->enqueue([$x, $y, $z]); + }else{ //light removed + $this->removalVisited[$blockHash] = true; + $this->removalQueue->enqueue([$x, $y, $z, $oldLevel]); + } } } } } public function execute(){ + $this->prepareNodes(); + while(!$this->removalQueue->isEmpty()){ list($x, $y, $z, $oldAdjacentLight) = $this->removalQueue->dequeue();