From bd1ec79bae8c88aa984e1c5f0c3313be5ae9b435 Mon Sep 17 00:00:00 2001 From: Dries C Date: Wed, 4 Dec 2024 14:02:00 +0100 Subject: [PATCH] Protocol changes for 1.21.50 (#279) --- src/CameraAimAssistPacket.php | 8 +- src/CameraAimAssistPresetsPacket.php | 79 ++++++++++ src/PacketHandlerDefaultImplTrait.php | 4 + src/PacketHandlerInterface.php | 2 + src/PacketPool.php | 1 + src/PlayerAuthInputPacket.php | 59 +++---- src/ProtocolInfo.php | 7 +- src/ResourcePacksInfoPacket.php | 11 +- src/serializer/BitSet.php | 145 ++++++++++++++++++ src/types/LevelSoundEvent.php | 14 +- src/types/PlayerAuthInputFlags.php | 6 + .../camera/CameraAimAssistCategories.php | 58 +++++++ src/types/camera/CameraAimAssistCategory.php | 43 ++++++ .../CameraAimAssistCategoryBlockPriority.php | 43 ++++++ .../CameraAimAssistCategoryEntityPriority.php | 43 ++++++ .../CameraAimAssistCategoryPriorities.php | 82 ++++++++++ src/types/camera/CameraAimAssistPreset.php | 115 ++++++++++++++ .../CameraAimAssistPresetItemSettings.php | 43 ++++++ src/types/camera/CameraPreset.php | 16 +- src/types/entity/EntityIds.php | 1 + .../ItemStackResponseSlotInfo.php | 7 +- .../resourcepacks/ResourcePackInfoEntry.php | 9 +- tests/phpunit/BitSetTest.php | 88 +++++++++++ 23 files changed, 842 insertions(+), 42 deletions(-) create mode 100644 src/CameraAimAssistPresetsPacket.php create mode 100644 src/serializer/BitSet.php create mode 100644 src/types/camera/CameraAimAssistCategories.php create mode 100644 src/types/camera/CameraAimAssistCategory.php create mode 100644 src/types/camera/CameraAimAssistCategoryBlockPriority.php create mode 100644 src/types/camera/CameraAimAssistCategoryEntityPriority.php create mode 100644 src/types/camera/CameraAimAssistCategoryPriorities.php create mode 100644 src/types/camera/CameraAimAssistPreset.php create mode 100644 src/types/camera/CameraAimAssistPresetItemSettings.php create mode 100644 tests/phpunit/BitSetTest.php diff --git a/src/CameraAimAssistPacket.php b/src/CameraAimAssistPacket.php index e15eab2b..8793f264 100644 --- a/src/CameraAimAssistPacket.php +++ b/src/CameraAimAssistPacket.php @@ -22,6 +22,7 @@ class CameraAimAssistPacket extends DataPacket implements ClientboundPacket{ public const NETWORK_ID = ProtocolInfo::CAMERA_AIM_ASSIST_PACKET; + private string $presetId; private Vector2 $viewAngle; private float $distance; private CameraAimAssistTargetMode $targetMode; @@ -30,8 +31,9 @@ class CameraAimAssistPacket extends DataPacket implements ClientboundPacket{ /** * @generate-create-func */ - public static function create(Vector2 $viewAngle, float $distance, CameraAimAssistTargetMode $targetMode, CameraAimAssistActionType $actionType) : self{ + public static function create(string $presetId, Vector2 $viewAngle, float $distance, CameraAimAssistTargetMode $targetMode, CameraAimAssistActionType $actionType) : self{ $result = new self; + $result->presetId = $presetId; $result->viewAngle = $viewAngle; $result->distance = $distance; $result->targetMode = $targetMode; @@ -39,6 +41,8 @@ public static function create(Vector2 $viewAngle, float $distance, CameraAimAssi return $result; } + public function getPresetId() : string{ return $this->presetId; } + public function getViewAngle() : Vector2{ return $this->viewAngle; } public function getDistance() : float{ return $this->distance; } @@ -48,6 +52,7 @@ public function getTargetMode() : CameraAimAssistTargetMode{ return $this->targe public function getActionType() : CameraAimAssistActionType{ return $this->actionType; } protected function decodePayload(PacketSerializer $in) : void{ + $this->presetId = $in->getString(); $this->viewAngle = $in->getVector2(); $this->distance = $in->getLFloat(); $this->targetMode = CameraAimAssistTargetMode::fromPacket($in->getByte()); @@ -55,6 +60,7 @@ protected function decodePayload(PacketSerializer $in) : void{ } protected function encodePayload(PacketSerializer $out) : void{ + $out->putString($this->presetId); $out->putVector2($this->viewAngle); $out->putLFloat($this->distance); $out->putByte($this->targetMode->value); diff --git a/src/CameraAimAssistPresetsPacket.php b/src/CameraAimAssistPresetsPacket.php new file mode 100644 index 00000000..4d6cdfe8 --- /dev/null +++ b/src/CameraAimAssistPresetsPacket.php @@ -0,0 +1,79 @@ + + * + * BedrockProtocol is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use pocketmine\network\mcpe\protocol\types\camera\CameraAimAssistCategories; +use pocketmine\network\mcpe\protocol\types\camera\CameraAimAssistPreset; +use function count; + +class CameraAimAssistPresetsPacket extends DataPacket implements ClientboundPacket{ + public const NETWORK_ID = ProtocolInfo::CAMERA_AIM_ASSIST_PRESETS_PACKET; + + /** @var CameraAimAssistCategories[] */ + private array $categories; + /** @var CameraAimAssistPreset[] */ + private array $presets; + + /** + * @generate-create-func + * @param CameraAimAssistCategories[] $categories + * @param CameraAimAssistPreset[] $presets + */ + public static function create(array $categories, array $presets) : self{ + $result = new self; + $result->categories = $categories; + $result->presets = $presets; + return $result; + } + + /** + * @return CameraAimAssistCategories[] + */ + public function getCategories() : array{ return $this->categories; } + + /** + * @return CameraAimAssistPreset[] + */ + public function getPresets() : array{ return $this->presets; } + + protected function decodePayload(PacketSerializer $in) : void{ + $this->categories = []; + for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){ + $this->categories[] = CameraAimAssistCategories::read($in); + } + + $this->presets = []; + for($i = 0, $count = $in->getUnsignedVarInt(); $i < $count; ++$i){ + $this->presets[] = CameraAimAssistPreset::read($in); + } + } + + protected function encodePayload(PacketSerializer $out) : void{ + $out->putUnsignedVarInt(count($this->categories)); + foreach($this->categories as $category){ + $category->write($out); + } + + $out->putUnsignedVarInt(count($this->presets)); + foreach($this->presets as $preset){ + $preset->write($out); + } + } + + public function handle(PacketHandlerInterface $handler) : bool{ + return $handler->handleCameraAimAssistPresets($this); + } +} diff --git a/src/PacketHandlerDefaultImplTrait.php b/src/PacketHandlerDefaultImplTrait.php index 9523649c..9bb58fb0 100644 --- a/src/PacketHandlerDefaultImplTrait.php +++ b/src/PacketHandlerDefaultImplTrait.php @@ -837,4 +837,8 @@ public function handleMovementEffect(MovementEffectPacket $packet) : bool{ public function handleSetMovementAuthority(SetMovementAuthorityPacket $packet) : bool{ return false; } + + public function handleCameraAimAssistPresets(CameraAimAssistPresetsPacket $packet) : bool{ + return false; + } } diff --git a/src/PacketHandlerInterface.php b/src/PacketHandlerInterface.php index ca966659..70d7fa93 100644 --- a/src/PacketHandlerInterface.php +++ b/src/PacketHandlerInterface.php @@ -425,4 +425,6 @@ public function handleContainerRegistryCleanup(ContainerRegistryCleanupPacket $p public function handleMovementEffect(MovementEffectPacket $packet) : bool; public function handleSetMovementAuthority(SetMovementAuthorityPacket $packet) : bool; + + public function handleCameraAimAssistPresets(CameraAimAssistPresetsPacket $packet) : bool; } diff --git a/src/PacketPool.php b/src/PacketPool.php index e9a4be85..8813e6e1 100644 --- a/src/PacketPool.php +++ b/src/PacketPool.php @@ -237,6 +237,7 @@ public function __construct(){ $this->registerPacket(new ContainerRegistryCleanupPacket()); $this->registerPacket(new MovementEffectPacket()); $this->registerPacket(new SetMovementAuthorityPacket()); + $this->registerPacket(new CameraAimAssistPresetsPacket()); } public function registerPacket(Packet $packet) : void{ diff --git a/src/PlayerAuthInputPacket.php b/src/PlayerAuthInputPacket.php index 0a7a0cf3..c03f9ca6 100644 --- a/src/PlayerAuthInputPacket.php +++ b/src/PlayerAuthInputPacket.php @@ -16,6 +16,7 @@ use pocketmine\math\Vector2; use pocketmine\math\Vector3; +use pocketmine\network\mcpe\protocol\serializer\BitSet; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\InputMode; use pocketmine\network\mcpe\protocol\types\InteractionMode; @@ -38,7 +39,7 @@ class PlayerAuthInputPacket extends DataPacket implements ServerboundPacket{ private float $headYaw; private float $moveVecX; private float $moveVecZ; - private int $inputFlags; + private BitSet $inputFlags; private int $inputMode; private int $playMode; private int $interactionMode; @@ -53,6 +54,7 @@ class PlayerAuthInputPacket extends DataPacket implements ServerboundPacket{ private float $analogMoveVecX; private float $analogMoveVecZ; private Vector3 $cameraOrientation; + private Vector2 $rawMove; /** * @generate-create-func @@ -65,7 +67,7 @@ private static function internalCreate( float $headYaw, float $moveVecX, float $moveVecZ, - int $inputFlags, + BitSet $inputFlags, int $inputMode, int $playMode, int $interactionMode, @@ -79,6 +81,7 @@ private static function internalCreate( float $analogMoveVecX, float $analogMoveVecZ, Vector3 $cameraOrientation, + Vector2 $rawMove, ) : self{ $result = new self; $result->position = $position; @@ -101,11 +104,12 @@ private static function internalCreate( $result->analogMoveVecX = $analogMoveVecX; $result->analogMoveVecZ = $analogMoveVecZ; $result->cameraOrientation = $cameraOrientation; + $result->rawMove = $rawMove; return $result; } /** - * @param int $inputFlags @see PlayerAuthInputFlags + * @param BitSet $inputFlags @see PlayerAuthInputFlags * @param int $inputMode @see InputMode * @param int $playMode @see PlayMode * @param int $interactionMode @see InteractionMode @@ -118,7 +122,7 @@ public static function create( float $headYaw, float $moveVecX, float $moveVecZ, - int $inputFlags, + BitSet $inputFlags, int $inputMode, int $playMode, int $interactionMode, @@ -131,22 +135,18 @@ public static function create( ?PlayerAuthInputVehicleInfo $vehicleInfo, float $analogMoveVecX, float $analogMoveVecZ, - Vector3 $cameraOrientation + Vector3 $cameraOrientation, + Vector2 $rawMove ) : self{ - $realInputFlags = $inputFlags & ~((1 << PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST) | (1 << PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION) | (1 << PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS)); - if($itemStackRequest !== null){ - $realInputFlags |= 1 << PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST; - } - if($itemInteractionData !== null){ - $realInputFlags |= 1 << PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION; - } - if($blockActions !== null){ - $realInputFlags |= 1 << PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS; - } - if($vehicleInfo !== null){ - $realInputFlags |= 1 << PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE; + if($inputFlags->getLength() !== 65){ + throw new \InvalidArgumentException("Input flags must be 65 bits long"); } + $inputFlags->set(PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST, $itemStackRequest !== null); + $inputFlags->set(PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION, $itemInteractionData !== null); + $inputFlags->set(PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS, $blockActions !== null); + $inputFlags->set(PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE, $vehicleInfo !== null); + return self::internalCreate( $position, $pitch, @@ -154,7 +154,7 @@ public static function create( $headYaw, $moveVecX, $moveVecZ, - $realInputFlags, + $inputFlags, $inputMode, $playMode, $interactionMode, @@ -167,7 +167,8 @@ public static function create( $vehicleInfo, $analogMoveVecX, $analogMoveVecZ, - $cameraOrientation + $cameraOrientation, + $rawMove ); } @@ -198,7 +199,7 @@ public function getMoveVecZ() : float{ /** * @see PlayerAuthInputFlags */ - public function getInputFlags() : int{ + public function getInputFlags() : BitSet{ return $this->inputFlags; } @@ -256,9 +257,7 @@ public function getAnalogMoveVecZ() : float{ return $this->analogMoveVecZ; } public function getCameraOrientation() : Vector3{ return $this->cameraOrientation; } - public function hasFlag(int $flag) : bool{ - return ($this->inputFlags & (1 << $flag)) !== 0; - } + public function getRawMove() : Vector2{ return $this->rawMove; } protected function decodePayload(PacketSerializer $in) : void{ $this->pitch = $in->getLFloat(); @@ -267,20 +266,20 @@ protected function decodePayload(PacketSerializer $in) : void{ $this->moveVecX = $in->getLFloat(); $this->moveVecZ = $in->getLFloat(); $this->headYaw = $in->getLFloat(); - $this->inputFlags = $in->getUnsignedVarLong(); + $this->inputFlags = BitSet::read($in, 65); $this->inputMode = $in->getUnsignedVarInt(); $this->playMode = $in->getUnsignedVarInt(); $this->interactionMode = $in->getUnsignedVarInt(); $this->interactRotation = $in->getVector2(); $this->tick = $in->getUnsignedVarLong(); $this->delta = $in->getVector3(); - if($this->hasFlag(PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION)){ + if($this->inputFlags->get(PlayerAuthInputFlags::PERFORM_ITEM_INTERACTION)){ $this->itemInteractionData = ItemInteractionData::read($in); } - if($this->hasFlag(PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST)){ + if($this->inputFlags->get(PlayerAuthInputFlags::PERFORM_ITEM_STACK_REQUEST)){ $this->itemStackRequest = ItemStackRequest::read($in); } - if($this->hasFlag(PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS)){ + if($this->inputFlags->get(PlayerAuthInputFlags::PERFORM_BLOCK_ACTIONS)){ $this->blockActions = []; $max = $in->getVarInt(); for($i = 0; $i < $max; ++$i){ @@ -292,12 +291,13 @@ protected function decodePayload(PacketSerializer $in) : void{ }; } } - if($this->hasFlag(PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE)){ + if($this->inputFlags->get(PlayerAuthInputFlags::IN_CLIENT_PREDICTED_VEHICLE)){ $this->vehicleInfo = PlayerAuthInputVehicleInfo::read($in); } $this->analogMoveVecX = $in->getLFloat(); $this->analogMoveVecZ = $in->getLFloat(); $this->cameraOrientation = $in->getVector3(); + $this->rawMove = $in->getVector2(); } protected function encodePayload(PacketSerializer $out) : void{ @@ -307,7 +307,7 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putLFloat($this->moveVecX); $out->putLFloat($this->moveVecZ); $out->putLFloat($this->headYaw); - $out->putUnsignedVarLong($this->inputFlags); + $this->inputFlags->write($out); $out->putUnsignedVarInt($this->inputMode); $out->putUnsignedVarInt($this->playMode); $out->putUnsignedVarInt($this->interactionMode); @@ -333,6 +333,7 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putLFloat($this->analogMoveVecX); $out->putLFloat($this->analogMoveVecZ); $out->putVector3($this->cameraOrientation); + $out->putVector2($this->rawMove); } public function handle(PacketHandlerInterface $handler) : bool{ diff --git a/src/ProtocolInfo.php b/src/ProtocolInfo.php index 177a8cb0..b373de66 100644 --- a/src/ProtocolInfo.php +++ b/src/ProtocolInfo.php @@ -32,11 +32,11 @@ private function __construct(){ */ /** Actual Minecraft: PE protocol version */ - public const CURRENT_PROTOCOL = 748; + public const CURRENT_PROTOCOL = 766; /** Current Minecraft PE version reported by the server. This is usually the earliest currently supported version. */ - public const MINECRAFT_VERSION = 'v1.21.40'; + public const MINECRAFT_VERSION = 'v1.21.50'; /** Version number sent to clients in ping responses. */ - public const MINECRAFT_VERSION_NETWORK = '1.21.40'; + public const MINECRAFT_VERSION_NETWORK = '1.21.50'; public const LOGIN_PACKET = 0x01; public const PLAY_STATUS_PACKET = 0x02; @@ -255,4 +255,5 @@ private function __construct(){ public const CONTAINER_REGISTRY_CLEANUP_PACKET = 0x13d; public const MOVEMENT_EFFECT_PACKET = 0x13e; public const SET_MOVEMENT_AUTHORITY_PACKET = 0x13f; + public const CAMERA_AIM_ASSIST_PRESETS_PACKET = 0x140; } diff --git a/src/ResourcePacksInfoPacket.php b/src/ResourcePacksInfoPacket.php index f8a40ff8..9e1f434f 100644 --- a/src/ResourcePacksInfoPacket.php +++ b/src/ResourcePacksInfoPacket.php @@ -16,6 +16,7 @@ use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackInfoEntry; +use Ramsey\Uuid\UuidInterface; use function count; class ResourcePacksInfoPacket extends DataPacket implements ClientboundPacket{ @@ -26,17 +27,21 @@ class ResourcePacksInfoPacket extends DataPacket implements ClientboundPacket{ public bool $mustAccept = false; //if true, forces client to choose between accepting packs or being disconnected public bool $hasAddons = false; public bool $hasScripts = false; //if true, causes disconnect for any platform that doesn't support scripts yet + private UuidInterface $worldTemplateId; + private string $worldTemplateVersion; /** * @generate-create-func * @param ResourcePackInfoEntry[] $resourcePackEntries */ - public static function create(array $resourcePackEntries, bool $mustAccept, bool $hasAddons, bool $hasScripts) : self{ + public static function create(array $resourcePackEntries, bool $mustAccept, bool $hasAddons, bool $hasScripts, UuidInterface $worldTemplateId, string $worldTemplateVersion) : self{ $result = new self; $result->resourcePackEntries = $resourcePackEntries; $result->mustAccept = $mustAccept; $result->hasAddons = $hasAddons; $result->hasScripts = $hasScripts; + $result->worldTemplateId = $worldTemplateId; + $result->worldTemplateVersion = $worldTemplateVersion; return $result; } @@ -44,6 +49,8 @@ protected function decodePayload(PacketSerializer $in) : void{ $this->mustAccept = $in->getBool(); $this->hasAddons = $in->getBool(); $this->hasScripts = $in->getBool(); + $this->worldTemplateId = $in->getUUID(); + $this->worldTemplateVersion = $in->getString(); $resourcePackCount = $in->getLShort(); while($resourcePackCount-- > 0){ @@ -55,6 +62,8 @@ protected function encodePayload(PacketSerializer $out) : void{ $out->putBool($this->mustAccept); $out->putBool($this->hasAddons); $out->putBool($this->hasScripts); + $out->putUUID($this->worldTemplateId); + $out->putString($this->worldTemplateVersion); $out->putLShort(count($this->resourcePackEntries)); foreach($this->resourcePackEntries as $entry){ $entry->write($out); diff --git a/src/serializer/BitSet.php b/src/serializer/BitSet.php new file mode 100644 index 00000000..1e4a3807 --- /dev/null +++ b/src/serializer/BitSet.php @@ -0,0 +1,145 @@ + + * + * BedrockProtocol is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\serializer; + +use function array_pad; +use function array_slice; +use function array_values; +use function count; +use function intdiv; + +class BitSet{ + private const INT_BITS = PHP_INT_SIZE * 8; + private const SHIFT = 7; + + /** + * @param int[] $parts + */ + public function __construct( + private readonly int $length, + private array $parts = [] + ){ + $expectedPartsCount = self::getExpectedPartsCount($length); + $partsCount = count($parts); + + if($partsCount > $expectedPartsCount){ + throw new \InvalidArgumentException("Too many parts"); + }elseif($partsCount < $expectedPartsCount){ + $parts = array_pad($parts, $expectedPartsCount, 0); + } + + $this->parts = array_values($parts); + } + + public function get(int $index) : bool{ + [$partIndex, $bitIndex] = $this->getPartIndex($index); + + return ($this->parts[$partIndex] & (1 << $bitIndex)) !== 0; + } + + public function set(int $index, bool $value) : void{ + [$partIndex, $bitIndex] = $this->getPartIndex($index); + + if($value){ + $this->parts[$partIndex] |= 1 << $bitIndex; + }else{ + $this->parts[$partIndex] &= ~(1 << $bitIndex); + } + } + + /** + * Returns the part index and the bit index within that part for a given bit index. + * + * @return array{int, int} + */ + private function getPartIndex(int $index) : array{ + if($index < 0 or $index >= $this->length){ + throw new \InvalidArgumentException("Index out of bounds"); + } + + return [ + intdiv($index, self::INT_BITS), + $index % self::INT_BITS + ]; + } + + /** + * @internal + */ + public function getPartsCount() : int{ + return count($this->parts); + } + + private static function getExpectedPartsCount(int $length) : int{ + return intdiv($length + self::INT_BITS - 1, self::INT_BITS); + } + + public static function read(PacketSerializer $in, int $length) : self{ + $result = [0]; + + $currentIndex = 0; + $currentShift = 0; + + for($i = 0; $i < $length; $i += self::SHIFT){ + $b = $in->getByte(); + $bits = $b & 0x7f; + + $result[$currentIndex] |= $bits << $currentShift; //extra bits will be discarded + $nextShift = $currentShift + self::SHIFT; + if($nextShift >= self::INT_BITS){ + $nextShift -= self::INT_BITS; + $rightShift = self::SHIFT - $nextShift; + $result[++$currentIndex] = $bits >> $rightShift; + } + $currentShift = $nextShift; + + if(($b & 0x80) === 0){ + return new self($length, array_slice($result, 0, self::getExpectedPartsCount($length))); + } + } + + return new self($length, array_slice($result, 0, self::getExpectedPartsCount($length))); + } + + public function write(PacketSerializer $out) : void{ + $parts = $this->parts; + $length = $this->length; + + $currentIndex = 0; + $currentShift = 0; + + for($i = 0; $i < $length; $i += self::SHIFT){ + $bits = $parts[$currentIndex] >> $currentShift; + $nextShift = $currentShift + self::SHIFT; + if($nextShift >= self::INT_BITS){ + $nextShift -= self::INT_BITS; + $bits |= ($parts[++$currentIndex] ?? 0) << (self::SHIFT - $nextShift); + } + $currentShift = $nextShift; + + $last = $i + self::SHIFT >= $length; + $bits |= $last ? 0 : 0x80; + + $out->putByte($bits); + if($last){ + break; + } + } + } + + public function getLength() : int{ + return $this->length; + } +} diff --git a/src/types/LevelSoundEvent.php b/src/types/LevelSoundEvent.php index 0ddba26e..872cd377 100644 --- a/src/types/LevelSoundEvent.php +++ b/src/types/LevelSoundEvent.php @@ -521,8 +521,20 @@ private function __construct(){ public const RECORD_PRECIPICE = 529; public const VAULT_REJECT_REWARDED_PLAYER = 530; public const IMITATE_DROWNED = 531; - + public const IMITATE_CREAKING = 532; public const BUNDLE_INSERT_FAIL = 533; + public const SPONGE_ABSORB = 534; + + public const BLOCK_CREAKING_HEART_TRAIL = 536; + public const CREAKING_HEART_SPAWN = 537; + public const ACTIVATE = 538; + public const DEACTIVATE = 539; + public const FREEZE = 540; + public const UNFREEZE = 541; + public const OPEN = 542; + public const OPEN_LONG = 543; + public const CLOSE = 544; + public const CLOSE_LONG = 545; //The following aliases are kept for backwards compatibility only public const SCULK_SENSOR_POWER_ON = self::POWER_ON_SCULK_SENSOR; diff --git a/src/types/PlayerAuthInputFlags.php b/src/types/PlayerAuthInputFlags.php index 60809451..b77e80d2 100644 --- a/src/types/PlayerAuthInputFlags.php +++ b/src/types/PlayerAuthInputFlags.php @@ -111,5 +111,11 @@ final class PlayerAuthInputFlags{ public const IS_ROT_CONTROLLED_BY_MOVE_DIRECTION = 55; public const START_SPIN_ATTACK = 56; public const STOP_SPIN_ATTACK = 57; + public const JUMP_RELEASED_RAW = 58; + public const JUMP_PRESSED_RAW = 59; + public const JUMP_CURRENT_RAW = 60; + public const SNEAK_RELEASED_RAW = 61; + public const SNEAK_PRESSED_RAW = 62; + public const SNEAK_CURRENT_RAW = 63; } diff --git a/src/types/camera/CameraAimAssistCategories.php b/src/types/camera/CameraAimAssistCategories.php new file mode 100644 index 00000000..f5a98874 --- /dev/null +++ b/src/types/camera/CameraAimAssistCategories.php @@ -0,0 +1,58 @@ + + * + * BedrockProtocol is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use function count; + +final class CameraAimAssistCategories{ + + /** + * @param CameraAimAssistCategory[] $categories + */ + public function __construct( + private string $identifier, + private array $categories + ){} + + public function getIdentifier() : string{ return $this->identifier; } + + /** + * @return CameraAimAssistCategory[] + */ + public function getCategories() : array{ return $this->categories; } + + public static function read(PacketSerializer $in) : self{ + $identifier = $in->getString(); + + $categories = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $categories[] = CameraAimAssistCategory::read($in); + } + + return new self( + $identifier, + $categories + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->identifier); + $out->putUnsignedVarInt(count($this->categories)); + foreach($this->categories as $category){ + $category->write($out); + } + } +} diff --git a/src/types/camera/CameraAimAssistCategory.php b/src/types/camera/CameraAimAssistCategory.php new file mode 100644 index 00000000..67e8af52 --- /dev/null +++ b/src/types/camera/CameraAimAssistCategory.php @@ -0,0 +1,43 @@ + + * + * BedrockProtocol is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +final class CameraAimAssistCategory{ + + public function __construct( + private string $name, + private CameraAimAssistCategoryPriorities $priorities + ){} + + public function getName() : string{ return $this->name; } + + public function getPriorities() : CameraAimAssistCategoryPriorities{ return $this->priorities; } + + public static function read(PacketSerializer $in) : self{ + $name = $in->getString(); + $priorities = CameraAimAssistCategoryPriorities::read($in); + return new self( + $name, + $priorities + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->name); + $this->priorities->write($out); + } +} diff --git a/src/types/camera/CameraAimAssistCategoryBlockPriority.php b/src/types/camera/CameraAimAssistCategoryBlockPriority.php new file mode 100644 index 00000000..9b4893e6 --- /dev/null +++ b/src/types/camera/CameraAimAssistCategoryBlockPriority.php @@ -0,0 +1,43 @@ + + * + * BedrockProtocol is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +final class CameraAimAssistCategoryBlockPriority{ + + public function __construct( + private string $identifier, + private int $priority + ){} + + public function getIdentifier() : string{ return $this->identifier; } + + public function getPriority() : int{ return $this->priority; } + + public static function read(PacketSerializer $in) : self{ + $identifier = $in->getString(); + $priority = $in->getLInt(); + return new self( + $identifier, + $priority + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->identifier); + $out->putLInt($this->priority); + } +} diff --git a/src/types/camera/CameraAimAssistCategoryEntityPriority.php b/src/types/camera/CameraAimAssistCategoryEntityPriority.php new file mode 100644 index 00000000..d6c7e1c8 --- /dev/null +++ b/src/types/camera/CameraAimAssistCategoryEntityPriority.php @@ -0,0 +1,43 @@ + + * + * BedrockProtocol is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +final class CameraAimAssistCategoryEntityPriority{ + + public function __construct( + private string $identifier, + private int $priority + ){} + + public function getIdentifier() : string{ return $this->identifier; } + + public function getPriority() : int{ return $this->priority; } + + public static function read(PacketSerializer $in) : self{ + $identifier = $in->getString(); + $priority = $in->getLInt(); + return new self( + $identifier, + $priority + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->identifier); + $out->putLInt($this->priority); + } +} diff --git a/src/types/camera/CameraAimAssistCategoryPriorities.php b/src/types/camera/CameraAimAssistCategoryPriorities.php new file mode 100644 index 00000000..838b7629 --- /dev/null +++ b/src/types/camera/CameraAimAssistCategoryPriorities.php @@ -0,0 +1,82 @@ + + * + * BedrockProtocol is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use function count; + +final class CameraAimAssistCategoryPriorities{ + + /** + * @param CameraAimAssistCategoryEntityPriority[] $entities + * @param CameraAimAssistCategoryBlockPriority[] $blocks + */ + public function __construct( + private array $entities, + private array $blocks, + private ?int $defaultEntityPriority, + private ?int $defaultBlockPriority + ){} + + /** + * @return CameraAimAssistCategoryEntityPriority[] + */ + public function getEntities() : array{ return $this->entities; } + + /** + * @return CameraAimAssistCategoryBlockPriority[] + */ + public function getBlocks() : array{ return $this->blocks; } + + public function getDefaultEntityPriority() : ?int{ return $this->defaultEntityPriority; } + + public function getDefaultBlockPriority() : ?int{ return $this->defaultBlockPriority; } + + public static function read(PacketSerializer $in) : self{ + $entities = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $entities[] = CameraAimAssistCategoryEntityPriority::read($in); + } + + $blocks = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $blocks[] = CameraAimAssistCategoryBlockPriority::read($in); + } + + $defaultEntityPriority = $in->readOptional(fn() => $in->getLInt()); + $defaultBlockPriority = $in->readOptional(fn() => $in->getLInt()); + return new self( + $entities, + $blocks, + $defaultEntityPriority, + $defaultBlockPriority + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putUnsignedVarInt(count($this->entities)); + foreach($this->entities as $entity){ + $entity->write($out); + } + + $out->putUnsignedVarInt(count($this->blocks)); + foreach($this->blocks as $block){ + $block->write($out); + } + + $out->writeOptional($this->defaultEntityPriority, fn(int $v) => $out->putLInt($v)); + $out->writeOptional($this->defaultBlockPriority, fn(int $v) => $out->putLInt($v)); + } +} diff --git a/src/types/camera/CameraAimAssistPreset.php b/src/types/camera/CameraAimAssistPreset.php new file mode 100644 index 00000000..77321537 --- /dev/null +++ b/src/types/camera/CameraAimAssistPreset.php @@ -0,0 +1,115 @@ + + * + * BedrockProtocol is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use function count; + +final class CameraAimAssistPreset{ + + /** + * @param string[] $exclusionList + * @param string[] $liquidTargetingList + * @param CameraAimAssistPresetItemSettings[] $itemSettings + */ + public function __construct( + private string $identifier, + private string $categories, + private array $exclusionList, + private array $liquidTargetingList, + private array $itemSettings, + private ?string $defaultItemSettings, + private ?string $defaultHandSettings, + ){} + + public function getIdentifier() : string{ return $this->identifier; } + + public function getCategories() : string{ return $this->categories; } + + /** + * @return string[] + */ + public function getExclusionList() : array{ return $this->exclusionList; } + + /** + * @return string[] + */ + public function getLiquidTargetingList() : array{ return $this->liquidTargetingList; } + + /** + * @return CameraAimAssistPresetItemSettings[] + */ + public function getItemSettings() : array{ return $this->itemSettings; } + + public function getDefaultItemSettings() : ?string{ return $this->defaultItemSettings; } + + public function getDefaultHandSettings() : ?string{ return $this->defaultHandSettings; } + + public static function read(PacketSerializer $in) : self{ + $identifier = $in->getString(); + $categories = $in->getString(); + + $exclusionList = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $exclusionList[] = $in->getString(); + } + + $liquidTargetingList = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $liquidTargetingList[] = $in->getString(); + } + + $itemSettings = []; + for($i = 0, $len = $in->getUnsignedVarInt(); $i < $len; ++$i){ + $itemSettings[] = CameraAimAssistPresetItemSettings::read($in); + } + + $defaultItemSettings = $in->readOptional(fn() => $in->getString()); + $defaultHandSettings = $in->readOptional(fn() => $in->getString()); + + return new self( + $identifier, + $categories, + $exclusionList, + $liquidTargetingList, + $itemSettings, + $defaultItemSettings, + $defaultHandSettings + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->identifier); + $out->putString($this->categories); + + $out->putUnsignedVarInt(count($this->exclusionList)); + foreach($this->exclusionList as $exclusion){ + $out->putString($exclusion); + } + + $out->putUnsignedVarInt(count($this->liquidTargetingList)); + foreach($this->liquidTargetingList as $liquidTargeting){ + $out->putString($liquidTargeting); + } + + $out->putUnsignedVarInt(count($this->itemSettings)); + foreach($this->itemSettings as $itemSetting){ + $itemSetting->write($out); + } + + $out->writeOptional($this->defaultItemSettings, fn(string $v) => $out->putString($v)); + $out->writeOptional($this->defaultHandSettings, fn(string $v) => $out->putString($v)); + } +} diff --git a/src/types/camera/CameraAimAssistPresetItemSettings.php b/src/types/camera/CameraAimAssistPresetItemSettings.php new file mode 100644 index 00000000..61e0e639 --- /dev/null +++ b/src/types/camera/CameraAimAssistPresetItemSettings.php @@ -0,0 +1,43 @@ + + * + * BedrockProtocol is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol\types\camera; + +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +final class CameraAimAssistPresetItemSettings{ + + public function __construct( + private string $itemIdentifier, + private string $categoryName, + ){} + + public function getItemIdentifier() : string{ return $this->itemIdentifier; } + + public function getCategoryName() : string{ return $this->categoryName; } + + public static function read(PacketSerializer $in) : self{ + $itemIdentifier = $in->getString(); + $categoryName = $in->getString(); + return new self( + $itemIdentifier, + $categoryName + ); + } + + public function write(PacketSerializer $out) : void{ + $out->putString($this->itemIdentifier); + $out->putString($this->categoryName); + } +} diff --git a/src/types/camera/CameraPreset.php b/src/types/camera/CameraPreset.php index fe8aec91..759836a4 100644 --- a/src/types/camera/CameraPreset.php +++ b/src/types/camera/CameraPreset.php @@ -35,12 +35,14 @@ public function __construct( private ?Vector2 $horizontalRotationLimit, private ?Vector2 $verticalRotationLimit, private ?bool $continueTargeting, + private ?float $blockListeningRadius, private ?Vector2 $viewOffset, private ?Vector3 $entityOffset, private ?float $radius, private ?int $audioListenerType, private ?bool $playerEffects, - private ?bool $alignTargetAndCameraForward + private ?bool $alignTargetAndCameraForward, + private ?bool $aimAssist, ){} public function getName() : string{ return $this->name; } @@ -67,6 +69,8 @@ public function getVerticalRotationLimit() : ?Vector2{ return $this->verticalRot public function getContinueTargeting() : ?bool{ return $this->continueTargeting; } + public function getBlockListeningRadius() : ?float{ return $this->blockListeningRadius; } + public function getViewOffset() : ?Vector2{ return $this->viewOffset; } public function getEntityOffset() : ?Vector3{ return $this->entityOffset; } @@ -79,6 +83,8 @@ public function getPlayerEffects() : ?bool{ return $this->playerEffects; } public function getAlignTargetAndCameraForward() : ?bool{ return $this->alignTargetAndCameraForward; } + public function getAimAssist() : ?bool{ return $this->aimAssist; } + public static function read(PacketSerializer $in) : self{ $name = $in->getString(); $parent = $in->getString(); @@ -92,12 +98,14 @@ public static function read(PacketSerializer $in) : self{ $horizontalRotationLimit = $in->readOptional($in->getVector2(...)); $verticalRotationLimit = $in->readOptional($in->getVector2(...)); $continueTargeting = $in->readOptional($in->getBool(...)); + $blockListeningRadius = $in->readOptional($in->getLFloat(...)); $viewOffset = $in->readOptional($in->getVector2(...)); $entityOffset = $in->readOptional($in->getVector3(...)); $radius = $in->readOptional($in->getLFloat(...)); $audioListenerType = $in->readOptional($in->getByte(...)); $playerEffects = $in->readOptional($in->getBool(...)); $alignTargetAndCameraForward = $in->readOptional($in->getBool(...)); + $aimAssist = $in->readOptional($in->getBool(...)); return new self( $name, @@ -112,12 +120,14 @@ public static function read(PacketSerializer $in) : self{ $horizontalRotationLimit, $verticalRotationLimit, $continueTargeting, + $blockListeningRadius, $viewOffset, $entityOffset, $radius, $audioListenerType, $playerEffects, - $alignTargetAndCameraForward + $alignTargetAndCameraForward, + $aimAssist ); } @@ -134,11 +144,13 @@ public function write(PacketSerializer $out) : void{ $out->writeOptional($this->horizontalRotationLimit, $out->putVector2(...)); $out->writeOptional($this->verticalRotationLimit, $out->putVector2(...)); $out->writeOptional($this->continueTargeting, $out->putBool(...)); + $out->writeOptional($this->blockListeningRadius, $out->putLFloat(...)); $out->writeOptional($this->viewOffset, $out->putVector2(...)); $out->writeOptional($this->entityOffset, $out->putVector3(...)); $out->writeOptional($this->radius, $out->putLFloat(...)); $out->writeOptional($this->audioListenerType, $out->putByte(...)); $out->writeOptional($this->playerEffects, $out->putBool(...)); $out->writeOptional($this->alignTargetAndCameraForward, $out->putBool(...)); + $out->writeOptional($this->aimAssist, $out->putBool(...)); } } diff --git a/src/types/entity/EntityIds.php b/src/types/entity/EntityIds.php index fd5ea7b8..04b2a692 100644 --- a/src/types/entity/EntityIds.php +++ b/src/types/entity/EntityIds.php @@ -48,6 +48,7 @@ private function __construct(){ public const COD = "minecraft:cod"; public const COMMAND_BLOCK_MINECART = "minecraft:command_block_minecart"; public const COW = "minecraft:cow"; + public const CREAKING = "minecraft:creaking"; public const CREEPER = "minecraft:creeper"; public const DOLPHIN = "minecraft:dolphin"; public const DONKEY = "minecraft:donkey"; diff --git a/src/types/inventory/stackresponse/ItemStackResponseSlotInfo.php b/src/types/inventory/stackresponse/ItemStackResponseSlotInfo.php index 7ddc2601..fa83941d 100644 --- a/src/types/inventory/stackresponse/ItemStackResponseSlotInfo.php +++ b/src/types/inventory/stackresponse/ItemStackResponseSlotInfo.php @@ -23,6 +23,7 @@ public function __construct( private int $count, private int $itemStackId, private string $customName, + private string $filteredCustomName, private int $durabilityCorrection ){} @@ -36,6 +37,8 @@ public function getItemStackId() : int{ return $this->itemStackId; } public function getCustomName() : string{ return $this->customName; } + public function getFilteredCustomName() : string{ return $this->filteredCustomName; } + public function getDurabilityCorrection() : int{ return $this->durabilityCorrection; } public static function read(PacketSerializer $in) : self{ @@ -44,8 +47,9 @@ public static function read(PacketSerializer $in) : self{ $count = $in->getByte(); $itemStackId = $in->readServerItemStackId(); $customName = $in->getString(); + $filteredCustomName = $in->getString(); $durabilityCorrection = $in->getVarInt(); - return new self($slot, $hotbarSlot, $count, $itemStackId, $customName, $durabilityCorrection); + return new self($slot, $hotbarSlot, $count, $itemStackId, $customName, $filteredCustomName, $durabilityCorrection); } public function write(PacketSerializer $out) : void{ @@ -54,6 +58,7 @@ public function write(PacketSerializer $out) : void{ $out->putByte($this->count); $out->writeServerItemStackId($this->itemStackId); $out->putString($this->customName); + $out->putString($this->filteredCustomName); $out->putVarInt($this->durabilityCorrection); } } diff --git a/src/types/resourcepacks/ResourcePackInfoEntry.php b/src/types/resourcepacks/ResourcePackInfoEntry.php index 0bb7b4c0..5752437d 100644 --- a/src/types/resourcepacks/ResourcePackInfoEntry.php +++ b/src/types/resourcepacks/ResourcePackInfoEntry.php @@ -15,10 +15,11 @@ namespace pocketmine\network\mcpe\protocol\types\resourcepacks; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; +use Ramsey\Uuid\UuidInterface; class ResourcePackInfoEntry{ public function __construct( - private string $packId, + private UuidInterface $packId, private string $version, private int $sizeBytes, private string $encryptionKey = "", @@ -30,7 +31,7 @@ public function __construct( private string $cdnUrl = "" ){} - public function getPackId() : string{ + public function getPackId() : UuidInterface{ return $this->packId; } @@ -65,7 +66,7 @@ public function isRtxCapable() : bool{ return $this->isRtxCapable; } public function getCdnUrl() : string{ return $this->cdnUrl; } public function write(PacketSerializer $out) : void{ - $out->putString($this->packId); + $out->putUUID($this->packId); $out->putString($this->version); $out->putLLong($this->sizeBytes); $out->putString($this->encryptionKey); @@ -78,7 +79,7 @@ public function write(PacketSerializer $out) : void{ } public static function read(PacketSerializer $in) : self{ - $uuid = $in->getString(); + $uuid = $in->getUUID(); $version = $in->getString(); $sizeBytes = $in->getLLong(); $encryptionKey = $in->getString(); diff --git a/tests/phpunit/BitSetTest.php b/tests/phpunit/BitSetTest.php new file mode 100644 index 00000000..c40b9a04 --- /dev/null +++ b/tests/phpunit/BitSetTest.php @@ -0,0 +1,88 @@ + + * + * BedrockProtocol is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +declare(strict_types=1); + +namespace pocketmine\network\mcpe\protocol; + +use PHPUnit\Framework\TestCase; +use pocketmine\network\mcpe\protocol\serializer\BitSet; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; + +class BitSetTest extends TestCase{ + + public function testBitSet() : void{ + $writeTest = new BitSet(65); + + $writeTest->set(0, true); + $writeTest->set(64, true); + + $packetSerializer = PacketSerializer::encoder(); + $writeTest->write($packetSerializer); + + $packetSerializer = PacketSerializer::decoder($packetSerializer->getBuffer(), 0); + $readTest = BitSet::read($packetSerializer, 65); + + self::assertEqualBitSets($writeTest, $readTest); + } + + public function testBitSetConstructor() : void{ + $test = new BitSet(65, [-9223372036854775807 - 1, 1]); + $test2 = new BitSet(65, [-9223372036854775807 - 1]); + + $test2->set(64, true); + + $packetSerializer = PacketSerializer::encoder(); + $test->write($packetSerializer); + + $packetSerializer2 = PacketSerializer::encoder(); + $test2->write($packetSerializer2); + + self::assertEquals($packetSerializer->getBuffer(), $packetSerializer2->getBuffer()); + } + + public function testBitSetParts() : void{ + $writeTest = new BitSet(128); + $writeTest->set(127, true); + + $packetSerializer = PacketSerializer::encoder(); + $writeTest->write($packetSerializer); + + $packetSerializer = PacketSerializer::decoder($packetSerializer->getBuffer(), 0); + $readTest = BitSet::read($packetSerializer, 128); + + self::assertEqualBitSets($writeTest, $readTest); + } + + public function testVarUnsignedLongCompatibility() : void{ + $packetSerializer = PacketSerializer::encoder(); + $packetSerializer->putUnsignedVarLong(0 | 1 << 63); + + $packetSerializer = PacketSerializer::decoder($packetSerializer->getBuffer(), 0); + $readTest = BitSet::read($packetSerializer, 64); + + $expectedResult = new BitSet(64); + $expectedResult->set(63, true); + + self::assertEqualBitSets($expectedResult, $readTest); + } + + private static function assertEqualBitSets(BitSet $a, BitSet $b) : void{ + self::assertEquals($length = $a->getLength(), $b->getLength(), "BitSet lengths are not equal"); + + for($i = 0; $i < $length; ++$i){ + self::assertEquals($a->get($i), $b->get($i), "BitSet values at index $i are not equal"); + } + + self::assertEquals($a->getPartsCount(), $b->getPartsCount(), "BitSet parts count is not equal"); + } +}