getAdjacentSupportType(Facing::DOWN)->hasEdgeSupport()){ return parent::place($tx, $item, $blockReplace, $blockClicked, $face, $clickVector, $player); } return false; } public function onPostPlace() : void{ $this->tryReconnect(); } /** * @param int[] $connections * @param int[][] $lookup * @phpstan-param array> $lookup */ protected static function searchState(array $connections, array $lookup) : ?int{ $shape = array_search($connections, $lookup, true); if($shape === false){ $shape = array_search(array_reverse($connections), $lookup, true); } return $shape === false ? null : $shape; } /** * Sets the rail shape according to the given connections, if a shape matches. * * @param int[] $connections * * @throws \InvalidArgumentException if no shape matches the given connections */ abstract protected function setShapeFromConnections(array $connections) : void; /** * Returns the connection directions of this rail (depending on the current block state) * * @return int[] */ abstract protected function getCurrentShapeConnections() : 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->getCurrentShapeConnections() as $connection){ $other = $this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND); $otherConnection = Facing::opposite($connection & ~RailConnectionInfo::FLAG_ASCEND); if(($connection & RailConnectionInfo::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 |= RailConnectionInfo::FLAG_ASCEND; } if( $other instanceof BaseRail && in_array($otherConnection, $other->getCurrentShapeConnections(), true) ){ $connections[] = $connection; } } return $connections; } /** * @param int[] $constraints * * @return true[] * @phpstan-return array */ 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 | RailConnectionInfo::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)); } } /** * @return true[] * @phpstan-return array */ protected function getPossibleConnectionDirectionsOneConstraint(int $constraint) : array{ $opposite = Facing::opposite($constraint & ~RailConnectionInfo::FLAG_ASCEND); $possible = [$opposite => true]; if(($constraint & RailConnectionInfo::FLAG_ASCEND) === 0){ //We can slope the other way if this connection isn't already a slope $possible[$opposite | RailConnectionInfo::FLAG_ASCEND] = true; } return $possible; } private function tryReconnect() : void{ $thisConnections = $this->getConnectedDirections(); $changed = false; $world = $this->position->getWorld(); do{ $possible = $this->getPossibleConnectionDirections($thisConnections); $continue = false; foreach($possible as $thisSide => $_){ $otherSide = Facing::opposite($thisSide & ~RailConnectionInfo::FLAG_ASCEND); $other = $this->getSide($thisSide & ~RailConnectionInfo::FLAG_ASCEND); if(($thisSide & RailConnectionInfo::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 |= RailConnectionInfo::FLAG_ASCEND; } if(!($other instanceof BaseRail) || 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->setConnections($otherConnections); $world->setBlock($other->position, $other); $changed = true; $thisConnections[] = $thisSide; $continue = count($thisConnections) < 2; break; //force recomputing possible directions, since this connection could invalidate others } } }while($continue); if($changed){ $this->setConnections($thisConnections); $world->setBlock($this->position, $this); } } /** * @param int[] $connections */ private function setConnections(array $connections) : void{ if(count($connections) === 1){ $connections[] = Facing::opposite($connections[0] & ~RailConnectionInfo::FLAG_ASCEND); }elseif(count($connections) !== 2){ throw new \InvalidArgumentException("Expected exactly 2 connections, got " . count($connections)); } $this->setShapeFromConnections($connections); } public function onNearbyBlockChange() : void{ $world = $this->position->getWorld(); if(!$this->getAdjacentSupportType(Facing::DOWN)->hasEdgeSupport()){ $world->useBreakOn($this->position); }else{ foreach($this->getCurrentShapeConnections() as $connection){ if(($connection & RailConnectionInfo::FLAG_ASCEND) !== 0 && !$this->getSide($connection & ~RailConnectionInfo::FLAG_ASCEND)->getSupportType(Facing::UP)->hasEdgeSupport()){ $world->useBreakOn($this->position); break; } } } } }