maxBits - $this->offset; if($bits > $bitsLeft){ throw new \InvalidArgumentException("No bits left in buffer (need $bits, have $bitsLeft"); } $value = ($this->value >> $this->offset) & ~(~0 << $bits); $this->offset += $bits; return $value; } public function int(int $bits, int &$value) : void{ $value = $this->readInt($bits); } protected function readBoundedInt(int $bits, int $min, int $max) : int{ $result = $this->readInt($bits) + $min; if($result < $min || $result > $max){ throw new InvalidSerializedRuntimeDataException("Value is outside the range $min - $max"); } return $result; } public function boundedInt(int $bits, int $min, int $max, int &$value) : void{ $value = $this->readBoundedInt($bits, $min, $max); } protected function readBool() : bool{ return $this->readInt(1) === 1; } public function bool(bool &$value) : void{ $value = $this->readBool(); } public function horizontalFacing(int &$facing) : void{ $facing = match($this->readInt(2)){ 0 => Facing::NORTH, 1 => Facing::EAST, 2 => Facing::SOUTH, 3 => Facing::WEST, default => throw new AssumptionFailedError("Unreachable") }; } /** * @param int[] $faces */ public function facingFlags(array &$faces) : void{ $result = []; foreach(Facing::ALL as $facing){ if($this->readBool()){ $result[$facing] = $facing; } } $faces = $result; } /** * @param int[] $faces */ public function horizontalFacingFlags(array &$faces) : void{ $result = []; foreach(Facing::HORIZONTAL as $facing){ if($this->readBool()){ $result[$facing] = $facing; } } $faces = $result; } public function facing(int &$facing) : void{ $facing = match($this->readInt(3)){ 0 => Facing::DOWN, 1 => Facing::UP, 2 => Facing::NORTH, 3 => Facing::SOUTH, 4 => Facing::WEST, 5 => Facing::EAST, default => throw new InvalidSerializedRuntimeDataException("Invalid facing value") }; } public function facingExcept(int &$facing, int $except) : void{ $result = 0; $this->facing($result); if($result === $except){ throw new InvalidSerializedRuntimeDataException("Illegal facing value"); } $facing = $result; } public function axis(int &$axis) : void{ $axis = match($this->readInt(2)){ 0 => Axis::X, 1 => Axis::Z, 2 => Axis::Y, default => throw new InvalidSerializedRuntimeDataException("Invalid axis value") }; } public function horizontalAxis(int &$axis) : void{ $axis = match($this->readInt(1)){ 0 => Axis::X, 1 => Axis::Z, default => throw new AssumptionFailedError("Unreachable") }; } /** * @param WallConnectionType[] $connections * @phpstan-param array $connections */ public function wallConnections(array &$connections) : void{ $result = []; $offset = 0; $packed = $this->readBoundedInt(7, 0, (3 ** 4) - 1); foreach(Facing::HORIZONTAL as $facing){ $type = intdiv($packed, (3 ** $offset)) % 3; if($type !== 0){ $result[$facing] = match($type){ 1 => WallConnectionType::SHORT, 2 => WallConnectionType::TALL, default => throw new AssumptionFailedError("Unreachable") }; } $offset++; } $connections = $result; } /** * @param BrewingStandSlot[] $slots * @phpstan-param array $slots */ public function brewingStandSlots(array &$slots) : void{ $result = []; foreach(BrewingStandSlot::cases() as $member){ if($this->readBool()){ $result[spl_object_id($member)] = $member; } } $slots = $result; } public function railShape(int &$railShape) : void{ $result = $this->readInt(4); if(!isset(RailConnectionInfo::CONNECTIONS[$result]) && !isset(RailConnectionInfo::CURVE_CONNECTIONS[$result])){ throw new InvalidSerializedRuntimeDataException("Invalid rail shape $result"); } $railShape = $result; } public function straightOnlyRailShape(int &$railShape) : void{ $result = $this->readInt(3); if(!isset(RailConnectionInfo::CONNECTIONS[$result])){ throw new InvalidSerializedRuntimeDataException("No rail shape matches meta $result"); } $railShape = $result; } public function getOffset() : int{ return $this->offset; } }