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..269b6f355 --- /dev/null +++ b/src/pocketmine/block/BaseRail.php @@ -0,0 +1,266 @@ + Vector3::SIDE_NORTH, + self::ASCENDING_EAST => Vector3::SIDE_EAST, + self::ASCENDING_SOUTH => Vector3::SIDE_SOUTH, + self::ASCENDING_WEST => Vector3::SIDE_WEST + ]; + + protected const FLAG_ASCEND = 1 << 24; //used to indicate direction-up + + protected const CONNECTIONS = [ + //straights + self::STRAIGHT_NORTH_SOUTH => [ + Vector3::SIDE_NORTH, + Vector3::SIDE_SOUTH + ], + self::STRAIGHT_EAST_WEST => [ + Vector3::SIDE_EAST, + Vector3::SIDE_WEST + ], + + //ascending + self::ASCENDING_EAST => [ + Vector3::SIDE_WEST, + Vector3::SIDE_EAST | self::FLAG_ASCEND + ], + self::ASCENDING_WEST => [ + Vector3::SIDE_EAST, + Vector3::SIDE_WEST | self::FLAG_ASCEND + ], + self::ASCENDING_NORTH => [ + Vector3::SIDE_SOUTH, + Vector3::SIDE_NORTH | self::FLAG_ASCEND + ], + self::ASCENDING_SOUTH => [ + Vector3::SIDE_NORTH, + Vector3::SIDE_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(Vector3::SIDE_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 = Vector3::getOppositeSide($connection & ~self::FLAG_ASCEND); + + if(($connection & self::FLAG_ASCEND) !== 0){ + $other = $other->getSide(Vector3::SIDE_UP); + + }elseif(!($other instanceof BaseRail)){ //check for rail sloping up to meet this one + $other = $other->getSide(Vector3::SIDE_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 = [ + Vector3::SIDE_NORTH => true, + Vector3::SIDE_SOUTH => true, + Vector3::SIDE_WEST => true, + Vector3::SIDE_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 = Vector3::getOppositeSide($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 = Vector3::getOppositeSide($thisSide & ~self::FLAG_ASCEND); + + $other = $this->getSide($thisSide & ~self::FLAG_ASCEND); + + if(($thisSide & self::FLAG_ASCEND) !== 0){ + $other = $other->getSide(Vector3::SIDE_UP); + + }elseif(!($other instanceof BaseRail)){ //check if other rails can slope up to meet this one + $other = $other->getSide(Vector3::SIDE_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[] = Vector3::getOppositeSide($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(Vector3::SIDE_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 8ac173cd3..eb66d98af 100644 --- a/src/pocketmine/block/Rail.php +++ b/src/pocketmine/block/Rail.php @@ -23,54 +23,71 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\item\Item; 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 => [ + Vector3::SIDE_SOUTH, + Vector3::SIDE_EAST + ], + self::CURVE_SOUTHWEST => [ + Vector3::SIDE_SOUTH, + Vector3::SIDE_WEST + ], + self::CURVE_NORTHWEST => [ + Vector3::SIDE_NORTH, + Vector3::SIDE_WEST + ], + self::CURVE_NORTHEAST => [ + Vector3::SIDE_NORTH, + Vector3::SIDE_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(Vector3::SIDE_DOWN)->isTransparent()){ - return $this->getLevel()->setBlock($blockReplace, $this, true, true); - } - - return false; - } - - public function onNearbyBlockChange() : void{ - if($this->getSide(Vector3::SIDE_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 = [ + Vector3::SIDE_NORTH, + Vector3::SIDE_SOUTH, + Vector3::SIDE_WEST, + Vector3::SIDE_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]; + } +}