diff --git a/README.md b/README.md index 685f06d..7b11780 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,14 @@ To send instructions to the camera, use the following code: - Set +
+ See demo + +https://github.com/user-attachments/assets/338e4b84-1ded-4424-9ffb-6e94298bc53c + +
+ + ```php use muqsit\libcamera\libcamera; use muqsit\libcamera\CameraInstruction; @@ -58,6 +66,7 @@ use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionEaseType; use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionRotation; use pocketmine\network\mcpe\protocol\types\camera\Vector3; use pocketmine\player\Player; +use muqsit\libcamera\CameraPresetRegistry; // ... if($player instanceof Player && $player->isOnline()){ @@ -69,15 +78,15 @@ if($player instanceof Player && $player->isOnline()){ * @phpstan-param Vector3|null $facing_pos */ CameraInstruction::set( - preset: libcamera::getPresetRegistry()->registered["target"], + preset: CameraPresetRegistry::TARGET(), ease: new CameraSetInstructionEase( CameraSetInstructionEaseType::IN_OUT_CUBIC, (float) 5.0 // duration (sec) ), - camera_pos: null, + camera_pos: $player->getPosition()->add(0.0, $player->getEyeHeight(), 0.0), //Without it, the camera will teleport into subspace rot: new CameraSetInstructionRotation( - (float)20.0, //pitch - (float)180.0 //yaw + (float)20, //pitch + (float)180 //yaw ), facing_pos: null )->send($player); @@ -86,6 +95,13 @@ if($player instanceof Player && $player->isOnline()){ - Fade +
+ See demo + +https://github.com/user-attachments/assets/01bfc489-16bd-4424-aad0-32abb81d7517 + +
+ ```php use muqsit\libcamera\libcamera; use muqsit\libcamera\CameraInstruction; @@ -95,6 +111,13 @@ use pocketmine\player\Player; // ... if($player instanceof Player && $player->isOnline()){ + $fadeInTime = 5; + $stayTime = 2; + $fadeOutTime = 2; + $r = 0; + $g = 0; + $b = 0; + /** * @phpstan-param CameraFadeInstructionColor|null $color * @phpstan-param CameraFadeInstructionTime|null $time @@ -108,24 +131,85 @@ if($player instanceof Player && $player->isOnline()){ - Target +After setting the camera to free mode, you need to explicitly assign a target. +This allows the camera to visually track a specific entity. +Unlike SetActorLinkPacket, it does not follow the entity automatically. + +
+ See demo + +https://github.com/user-attachments/assets/38cd6bf1-f666-4635-870b-2d51b12bfa3f + +
+ ```php -use muqsit\libcamera\libcamera; +use pocketmine\event\player\PlayerInteractEvent; use muqsit\libcamera\CameraInstruction; -use pocketmine\network\mcpe\protocol\types\camera\CameraTargetInstruction; +use pocketmine\entity\Zombie; +use muqsit\libcamera\libcamera; +use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionEase; +use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionEaseType; +use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionRotation; use pocketmine\math\Vector3; -use pocketmine\player\Player; - +use muqsit\libcamera\CameraPresetRegistry; // ... -if($player instanceof Player && $player->isOnline()){ - /** - * @phpstan-param Vector3|null $targetCenterOffset - * @phpstan-param int $actorUniqueId - */ - CameraInstruction::target( - targetCenterOffset: Vector3::zero(), // no offset - actorUniqueId: $player->getId() // for example target the player - )->send($player); -} + + /** @var array */ + private $set = []; + + public function ina(PlayerInteractEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + + //Removes camera tracking. Note that target and free cameras are managed separately. + if(isset($this->set[$player->getName()])){ + CameraInstruction::removeTarget()->send($player); + CameraInstruction::clear()->send($player); + unset($this->set[$player->getName()]); + return; + } + + //Find the most different zombie entities + $nearest = null; + $nearestDistance = PHP_INT_MAX; + foreach($player->getWorld()->getEntities() as $entity){ + if($entity instanceof Zombie){ + $distance = $player->getPosition()->distance($entity->getPosition()); + if($nearestDistance >= $distance){ + $nearest = $entity; + $nearestDistance = $distance; + } + } + } + + if($nearest === null){ + $player->sendMessage("No Zombie"); + return; + } + + // + CameraInstruction::set( + preset: CameraPresetRegistry::TARGET(), + ease: null, + camera_pos: $player->getPosition()->add(0, $player->getEyeHeight(), 0), + rot: new CameraSetInstructionRotation( + (float) $player->getLocation()->getPitch(), //pitch + (float) $player->getLocation()->getYaw() //yaw + ), + facing_pos: null + )->send($player); + + //To use CameraInstruction::target you first need to make it a free camera. + CameraInstruction::target( + targetCenterOffset: Vector3::zero(), // no offset + actorUniqueId: $nearest->getId() // for example target the player + )->send($player); + + //Manages which packets have been sent + $this->set[$player->getName()] = true; + } ``` - Remove Target @@ -156,6 +240,8 @@ if($player instanceof Player && $player->isOnline()){ - Multi +This doesn't work + ```php use muqsit\libcamera\libcamera; use muqsit\libcamera\CameraInstruction; @@ -179,12 +265,1268 @@ if($player instanceof Player && $player->isOnline()){ } ``` +- registerCustom Preset + +First, build a custom preset registry +CustomCameraPresetRegistry.php +```php + */ + public static function getAll() : array{ + return self::_registryGetAll(); + } + + protected static function setup() : void{ + self::register("CUSTOM_THIRD_PERSON_FRONT", new CameraPreset( + name: "minecraft:test", + parent: "minecraft:third_person_front", + xPosition: null, + yPosition: null, + zPosition: null, + pitch: null, + yaw: null, + rotationSpeed: null, + snapToTarget: null, + horizontalRotationLimit: null, + verticalRotationLimit: null, + continueTargeting: null, + blockListeningRadius: null, + viewOffset: null, + entityOffset: null, + radius: null, + yawLimitMin: null, + yawLimitMax: null, + audioListenerType: CameraPreset::AUDIO_LISTENER_TYPE_CAMERA, + playerEffects: false, + aimAssist: null, + controlScheme: null + )); + } +} +``` + +Next, register the camera preset in the library. + +```php +use muqsit\libcamera\libcamera; + +// ... + +libcamera::registerPreset(CustomCameraPresetRegistry::CUSTOM_THIRD_PERSON_FRONT()); +``` + +Third, use `CustomCameraPresetRegistry::CUSTOM_THIRD_PERSON_FRONT()` directly. + +```php +use muqsit\libcamera\libcamera; +use muqsit\libcamera\CameraInstruction; +use pocketmine\network\mcpe\protocol\types\camera\CameraPreset; +use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionEase; +use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionEaseType; +use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionRotation; +use pocketmine\network\mcpe\protocol\types\camera\Vector3; +use pocketmine\player\Player; +use muqsit\libcamera\CameraPresetRegistry; + +// ... +if($player instanceof Player && $player->isOnline()){ + /** + * @phpstan-param CameraPreset $preset + * @phpstan-param CameraSetInstructionEase|null $ease + * @phpstan-param Vector3|null $camera_pos + * @phpstan-param CameraSetInstructionRotation|null $rot + * @phpstan-param Vector3|null $facing_pos + */ + CameraInstruction::set( + preset: CustomCameraPresetRegistry::CUSTOM_FREE(), + ease: new CameraSetInstructionEase( + CameraSetInstructionEaseType::IN_OUT_CUBIC, + (float) 5.0 // duration (sec) + ), + camera_pos: $player->getPosition()->add(0.0, $player->getEyeHeight(), 0.0), //Without it, the camera will teleport into subspace + rot: new CameraSetInstructionRotation( + (float)20.0, //pitch + (float)180.0 //yaw + ), + facing_pos: null + )->send($player); +} +``` + +# camera technic + +- use + +```php +use pocketmine\event\player\PlayerItemUseEvent; +use muqsit\libcamera\CameraInstruction; +use muqsit\libcamera\libcamera; +use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionEase; +use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionEaseType; +use pocketmine\network\mcpe\protocol\types\camera\CameraSetInstructionRotation; +use pocketmine\player\Player; +use pocketmine\scheduler\ClosureTask; +use pocketmine\entity\Zombie; +use muqsit\libcamera\CameraPresetRegistry; +``` + +# Eases + +Usage Example in Minecraft: +When using minecraft:free with an ease parameter, you can move the free camera smoothly to a specified endpoint over a given duration. +The easing functions listed above determine how the camera moves. +Here, "in" means the start point and "out" means the end point. + +Reference: +https://bacchigames.club/mc/howtocamera.html + +> [!WARNING] +> These demos may stimulate the vestibular system and cause symptoms such as **dizziness or a sensation of head movement (headshake)** that could last for several hours. +> Please proceed only if you are aware of the risks and feel well enough to continue. + + +| Easing Name | Behavior Description | See Demo | IsCrash | +|----------------|----------------------------------------------------------------|-----------------------------|---------| +| in_back | Moves slightly backward before heading to the endpoint | [see](#in_back-demo) | ❌ | +| out_back | Slightly overshoots the endpoint and returns | [see](#out_back-demo) | ✅ | +| in_out_back | Combines both in and out behaviors | [see](#in_out_back-demo) | ✅ | +| in_bounce | Bounces 3 times before heading to the endpoint on the 4th time | [see](#in_bounce-demo) | ❌ | +| out_bounce | Bounces 3 times and stops at the endpoint on the 4th time | [see](#out_bounce-demo) | ❌ | +| in_out_bounce | Combines both in and out bounce behaviors | [see](#in_out_bounce-demo) | ❌ | +| in_circ | Accelerates toward the endpoint | [see](#in_circ-demo) | ❌ | +| out_circ | Decelerates toward the endpoint | [see](#out_circ-demo) | ❌ | +| in_out_circ | Combines both in and out circ behaviors | [see](#in_out_circ-demo) | ❌ | +| in_cubic | Accelerates toward the endpoint | [see](#in_cubic-demo) | ❌ | +| out_cubic | Decelerates toward the endpoint | [see](#out_cubic-demo) | ❌ | +| in_out_cubic | Combines both in and out cubic behaviors | [see](#in_out_cubic-demo) | ❌ | +| in_elastic | Oscillates 3 times and heads to the endpoint on the 4th time | [see](#in_elastic-demo) | ❌ | +| out_elastic | Oscillates 3 times and stops at the endpoint on the 4th time | [see](#out_elastic-demo) | ✅ | +| in_out_elastic | Combines both in and out elastic behaviors | [see](#in_out_elastic-demo) | ✅ | +| in_expo | Accelerates toward the endpoint | [see](#in_expo-demo) | ❌ | +| out_expo | Decelerates toward the endpoint | [see](#out_expo-demo) | ❌ | +| in_out_expo | Combines both in and out expo behaviors | [see](#in_out_expo-demo) | ❌ | +| in_quad | Accelerates toward the endpoint | [see](#in_quad-demo) | ❌ | +| out_quad | Decelerates toward the endpoint | [see](#out_quad-demo) | ❌ | +| in_out_quad | Combines both in and out quad behaviors | [see](#in_out_quad-demo) | ❌ | +| in_quart | Accelerates toward the endpoint | [see](#in_quart-demo) | ❌ | +| out_quart | Decelerates toward the endpoint | [see](#out_quart-demo) | ❌ | +| in_out_quart | Combines both in and out quart behaviors | [see](#in_out_quart-demo) | ❌ | +| in_quint | Accelerates toward the endpoint | [see](#in_quint-demo) | ❌ | +| out_quint | Decelerates toward the endpoint | [see](#out_quint-demo) | ❌ | +| in_out_quint | Combines both in and out quint behaviors | [see](#in_out_quint-demo) | ❌ | +| in_sine | Accelerates toward the endpoint | [see](#in_sine-demo) | ❌ | +| out_sine | Decelerates toward the endpoint | [see](#out_sine-demo) | ❌ | +| in_out_sine | Combines both in and out sine behaviors | [see](#in_out_sine-demo) | ❌ | +| linear | Moves from start to end at constant speed | [see](#linear-demo) | ❌ | +| spring | Slightly oscillates around the endpoint | [see](#spring-demo) | ✅ | + + + +## facing_pos + +Do you want to move while looking at a point? Use facing_pos! + +
+ See demo + +https://github.com/user-attachments/assets/1f5d73c2-073a-4777-8b13-8ee6c6badefb + +
+ +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + if($player instanceof Player&&$player->isOnline()){ + //linear + + //Find the most different zombie entities + $nearest = null; + $nearestDistance = PHP_INT_MAX; + foreach($player->getWorld()->getEntities() as $entity){ + if($entity instanceof Zombie){ + $distance = $player->getPosition()->distance($entity->getPosition()); + if($nearestDistance >= $distance){ + $nearest = $entity; + $nearestDistance = $distance; + } + } + } + + if($nearest === null){ + $player->sendMessage("No Zombie"); + return; + } + + + $do = $player->getDirectionVector()->multiply(10); + + CameraInstruction::multi( + CameraInstruction::set( + preset: CameraPresetRegistry::FREE(), + ease: null, + camera_pos: $player->getPosition()->add(0.0, $player->getEyeHeight(), 0.0), //Without it, the camera will teleport into subspace + rot: new CameraSetInstructionRotation( + (float) $player->getLocation()->getPitch(), //pitch + (float) $player->getLocation()->getYaw() //yaw + ), + facing_pos: $nearest->getLocation()->asVector3()->add(0, $nearest->getEyeHeight(), 0), + ), + CameraInstruction::set( + preset: CameraPresetRegistry::FREE(), + ease: new CameraSetInstructionEase( + CameraSetInstructionEaseType::LINEAR, + (float) 7.0 // duration (sec) + ), + camera_pos: $player->getPosition()->add(0.0, $player->getEyeHeight(), 0.0)->addVector($do), //Without it, the camera will teleport into subspace + rot: new CameraSetInstructionRotation( + (float) $player->getLocation()->getPitch(), //pitch + (float) $player->getLocation()->getYaw() //yaw + ), + facing_pos: $nearest->getLocation()->asVector3()->add(0, $nearest->getEyeHeight(), 0), + ) + )->send($player); + + $this->getScheduler()->scheduleDelayedTask(new ClosureTask(function() use ($player){ + CameraInstruction::clear()->send($player); + }), 20 * 7); + } + } +``` + +# demos + +For convenience, we will use this common code for the demo. + +```php + public function onDemo(Entity $player, int $easeType) : void{ + if($player instanceof Player&&$player->isOnline()){ + //Find the most different zombie entities + $nearest = null; + $nearestDistance = PHP_INT_MAX; + foreach($player->getWorld()->getEntities() as $entity){ + if($entity instanceof Zombie){ + $distance = $player->getPosition()->distance($entity->getPosition()); + if($nearestDistance >= $distance){ + $nearest = $entity; + $nearestDistance = $distance; + } + } + } + + if($nearest === null){ + $player->sendMessage("No Zombie"); + return; + } + + $player->setInvisible(true); + $nearest->despawnFrom($player); + + CameraInstruction::multi( + CameraInstruction::set( + preset: CameraPresetRegistry::FREE(), + ease: null, + camera_pos: $player->getPosition()->add(0.0, $player->getEyeHeight(), 0.0), //Without it, the camera will teleport into subspace + rot: new CameraSetInstructionRotation( + (float) $player->getLocation()->getPitch(), //pitch + (float) $player->getLocation()->getYaw() //yaw + ), + facing_pos:null, + ), + CameraInstruction::set( + preset: CameraPresetRegistry::FREE(), + ease: new CameraSetInstructionEase( + $easeType, + (float) 10.0 // duration (sec) + ), + camera_pos: $nearest->getPosition()->add(0.0, $player->getEyeHeight(), 0.0), //Without it, the camera will teleport into subspace + rot: new CameraSetInstructionRotation( + (float) $player->getLocation()->getPitch(), //pitch + (float) $player->getLocation()->getYaw() //yaw + ), + facing_pos: null, + ) + )->send($player); + + $this->getScheduler()->scheduleDelayedTask(new ClosureTask(function() use ($nearest, $player){ + CameraInstruction::clear()->send($player); + $player->setInvisible(false); + $nearest->despawnFrom($player); + }), 20 * 10); + } + } +``` + + +## in_back-demo + +
+ see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_BACK); +} +``` + +
+ + + +
+ See demo + +https://github.com/user-attachments/assets/67924c3f-f7c8-4216-b11f-943bf5149de9 + +
+ + + +## out_back-demo + + +### WARNING: ⚠️ Crashes client on 1.21.93 + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::OUT_BACK); +} +``` + +
+ + + + + + +## in_out_back-demo + +### WARNING: ⚠️ Crashes client on 1.21.93 + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_OUT_BACK); +} +``` + +
+ + + + + + + + +## in_bounce-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_BOUNCE); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/4f55e402-e27f-4e20-897a-3fd199561498 + +
+ + + +## out_bounce-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::OUT_BOUNCE); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/365555eb-6193-4121-ac71-b300b857edae + +
+ + + +## in_out_bounce-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_OUT_BOUNCE); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/a4a8f132-d31f-4187-9769-b5ea544a5d56 + +
+ + + +## in_circ-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_CIRC); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/8edb854e-ad07-47d6-ab08-792954209ffe + +
+ + + +## out_circ-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::OUT_CIRC); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/73c3bae0-1026-49be-855f-fdf53120bb55 + +
+ + + +## in_out_circ-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_OUT_CIRC); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/76626fef-d97a-4ed7-aa8a-d54a069c073c + +
+ + + +## in_cubic-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_CUBIC); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/1ec8c85b-2a92-4cf2-9f3c-6b4aa8b150db + +
+ + + +## out_cubic-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::OUT_CUBIC); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/02d67db4-d6c8-4062-b1f1-9b81f0e99012 + +
+ + + +## in_out_cubic-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_OUT_CUBIC); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/002867af-8f93-4ca7-b34b-5ab0a9a17855 + +
+ + + +## in_quart-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_QUART); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/01342b32-bd33-4bd3-a0e2-a332914fcd3d + + + +
+ + + +## out_quart-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::OUT_QUART); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/77c0d077-da10-41c1-a010-3f4aa62a984e + +
+ + + +## in_out_quart-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_OUT_QUART); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/cc72e54d-38fd-4921-89ea-48cc5c5d1f44 + +
+ + +## in_quint-demo + +
+ +see Code + +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_QUINT); + } +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/a50bb0d6-770f-4910-be82-05abda073c61 + +
+ + + +## out_quint-demo + +
+ +see Code + +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::OUT_QUINT); + } +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/f921aaa7-e170-4624-964f-b54c9a58b3c2 + +
+ + + +## in_out_quint-demo + +
+ +see Code + +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_OUT_QUINT); + } +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/db433b41-6912-49e2-af7c-7f556d5b38f4 + +
+ + + +## in_sine-demo + +
+ +see Code + +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_SINE); + } +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/758fcb54-db91-4d83-baa6-b62c724a7ae3 + +
+ + + +## out_sine-demo + +
+ +see Code + +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::OUT_SINE); + } +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/e686b103-274c-4d98-ba18-feccfa563d97 + +
+ + + +## in_out_sine-demo + +
+ +see Code + +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_OUT_SINE); + } +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/c32a1034-eec0-4d2c-8a20-33bd03060d88 + +
+ + + +## in_expo-demo + +
+ +see Code + +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_EXPO); + } +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/5ae64606-c404-4136-a78b-bee052ba0300 + +
+ + + +## out_expo-demo + +
+ +see Code + +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::OUT_EXPO); + } +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/97958704-f087-4894-9888-360e9cf9e758 + +
+ + + +## in_out_expo-demo + +
+ +see Code + +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_OUT_EXPO); + } +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/d2b91349-08b2-4273-91e2-f60d14d76423 + +
+ + +## linear-demo + +Want to move the camera freely? Use ease! + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + if($player instanceof Player&&$player->isOnline()){ + //linear + + $do = $player->getDirectionVector()->multiply(10); + + CameraInstruction::multi( + CameraInstruction::set( + preset: CameraPresetRegistry::FREE(), + ease: null, + camera_pos: $player->getPosition()->add(0.0, $player->getEyeHeight(), 0.0), //Without it, the camera will teleport into subspace + rot: new CameraSetInstructionRotation( + (float) $player->getLocation()->getPitch(), //pitch + (float) $player->getLocation()->getYaw() //yaw + ), + facing_pos: null + ), + CameraInstruction::set( + preset: CameraPresetRegistry::FREE(), + ease: new CameraSetInstructionEase( + CameraSetInstructionEaseType::LINEAR, + (float) 7.0 // duration (sec) + ), + camera_pos: $player->getPosition()->add(0.0, $player->getEyeHeight(), 0.0)->addVector($do), //Without it, the camera will teleport into subspace + rot: new CameraSetInstructionRotation( + (float) $player->getLocation()->getPitch(), //pitch + (float) $player->getLocation()->getYaw() //yaw + ), + facing_pos: null + ) + )->send($player); + + $this->getScheduler()->scheduleDelayedTask(new ClosureTask(function() use ($player){ + CameraInstruction::clear()->send($player); + }), 20 * 7); + } +} +``` + +
+ +
+ See demo + +https://github.com/user-attachments/assets/d5ca8d67-1ac6-4d2c-8051-db3455317cd6 + +
+ +## spring-demo + +### WARNING: ⚠️ Crashes client on 1.21.93 + +
+ +see Code + +```php + public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::SPRING); + } +``` + +
+ + + +## in_quad-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_QUAD); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/ccecf930-6c4e-46c8-b009-c88578d0668e + +
+ + + +## out_quad-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::OUT_QUAD); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/21d17561-ebb5-44fa-a95e-c4382504f834 + +
+ + + +## in_out_quad-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_OUT_QUAD); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/76d050d3-6c5e-47f3-9ca8-5386e3e17f00 + +
+ + + +## in_out_elastic-demo + +### WARNING: ⚠️ Crashes client on 1.21.93 + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_OUT_ELASTIC); +} +``` + +
+ + + +## out_elastic-demo + +### WARNING: ⚠️ Crashes client on 1.21.93 + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::OUT_ELASTIC); +} +``` + +
+ + + +## in_elastic-demo + +
+ +see Code + +```php +public function onUse(PlayerItemUseEvent $event) : void{ + $player = $event->getPlayer(); + if(!$player->isSneaking()){ + return; + } + $this->onDemo($player, CameraSetInstructionEaseType::IN_ELASTIC); +} +``` + +
+ + + +
+See demo + +https://github.com/user-attachments/assets/2290dead-b798-41f5-8cc7-b6d3f28a3760 + +
+ + + ## Roadmap At the moment, there are a few improvements that can be/or are being worked on. Here is a list of some of those improvements: -- [ ] Allow registering new camera presets +- [x] Allow registering new camera presets ## Issues diff --git a/src/muqsit/libcamera/CameraInstruction.php b/src/muqsit/libcamera/CameraInstruction.php index dcb5933..40c66e5 100644 --- a/src/muqsit/libcamera/CameraInstruction.php +++ b/src/muqsit/libcamera/CameraInstruction.php @@ -36,8 +36,32 @@ public static function set( ?CameraSetInstructionRotation $rot = null, ?Vector3 $facing_pos = null ) : self{ - $preset_id = libcamera::getPresetRegistry()->network_ids[spl_object_id($preset)]; - $instruction = new CameraSetInstruction($preset_id, $ease, $camera_pos, $rot, $facing_pos, null, null, null); + if(!libcamera::isRegistered()){ + $logger = \GlobalLogger::get(); + $logger = new \PrefixedLogger($logger, "libcamera"); + $logger->info("WARNING: libcamera is not registered."); + $logger->info("You must call libcamera::register(\$plugin) before using any camera instructions."); + $logger->info("Example:"); + $logger->info("public function onEnable() : void{"); + $logger->info(" if(!AwaitForm::isRegistered()){ "); + $logger->info(" AwaitForm::register(\$this);"); + $logger->info(" }"); + $logger->info("}"); + + throw new \RuntimeException("libcamera::register() must be called before using CameraInstruction::set()"); + } + $preset_id = libcamera::$network_ids[spl_object_id($preset)][1] ?? throw new \InvalidArgumentException("Unknown camera preset, see libcamera::registerPreset()"); + $instruction = new CameraSetInstruction( + preset: $preset_id, + ease: $ease, + cameraPosition: $camera_pos, + rotation: $rot, + facingPosition: $facing_pos, + viewOffset: null, + entityOffset: null, + default: null, + ignoreStartingValuesComponent: false + ); return new self([[$instruction, null, null, null]]); } diff --git a/src/muqsit/libcamera/CameraPresetRegistry.php b/src/muqsit/libcamera/CameraPresetRegistry.php index 2e11141..2c099e3 100644 --- a/src/muqsit/libcamera/CameraPresetRegistry.php +++ b/src/muqsit/libcamera/CameraPresetRegistry.php @@ -5,24 +5,159 @@ namespace muqsit\libcamera; use pocketmine\network\mcpe\protocol\types\camera\CameraPreset; -use function spl_object_id; +use pocketmine\math\Vector2; +use pocketmine\utils\RegistryTrait; +/** + * @method static CameraPreset FREE() + * @method static CameraPreset FIRST_PERSON() + * @method static CameraPreset THIRD_PERSON() + * @method static CameraPreset THIRD_PERSON_FRONT() + * @method static CameraPreset TARGET() + */ final class CameraPresetRegistry{ + use RegistryTrait; + + final private function __construct(){ + //none + } - /** @var array */ - readonly public array $network_ids; /** - * @param array $registered + * @internal Injecting this function will not send more presets! + * @see libcamera::registerPreset() */ - public function __construct( - readonly public array $registered - ){ - $network_ids = []; - $id = 0; - foreach($this->registered as $preset){ - $network_ids[spl_object_id($preset)] = $id++; - } - $this->network_ids = $network_ids; + private static function register(string $name, CameraPreset $member) : void{ + self::_registryRegister($name, $member); + } + + /** @return array */ + public static function getAll() : array{ + return self::_registryGetAll(); + } + + protected static function setup() : void{ + self::register("free", new CameraPreset( + name: "minecraft:free", + parent: "", + xPosition: null, + yPosition: null, + zPosition: null, + pitch: null, + yaw: null, + rotationSpeed: null, + snapToTarget: null, + horizontalRotationLimit: null, + verticalRotationLimit: null, + continueTargeting: null, + blockListeningRadius: null, + viewOffset: null, + entityOffset: null, + radius: null, + yawLimitMin: null, + yawLimitMax: null, + audioListenerType: CameraPreset::AUDIO_LISTENER_TYPE_CAMERA, + playerEffects: false, + aimAssist: null, + controlScheme: null + )); + + self::register("first_person", new CameraPreset( + name: "minecraft:first_person", + parent: "", + xPosition: null, + yPosition: null, + zPosition: null, + pitch: null, + yaw: null, + rotationSpeed: null, + snapToTarget: null, + horizontalRotationLimit: null, + verticalRotationLimit: null, + continueTargeting: null, + blockListeningRadius: null, + viewOffset: null, + entityOffset: null, + radius: null, + yawLimitMin: null, + yawLimitMax: null, + audioListenerType: CameraPreset::AUDIO_LISTENER_TYPE_PLAYER, + playerEffects: false, + aimAssist: null, + controlScheme: null + )); + self::register("third_person", new CameraPreset( + name: "minecraft:third_person", + parent: "", + xPosition: null, + yPosition: null, + zPosition: null, + pitch: null, + yaw: null, + rotationSpeed: null, + snapToTarget: null, + horizontalRotationLimit: null, + verticalRotationLimit: null, + continueTargeting: null, + blockListeningRadius: null, + viewOffset: null, + entityOffset: null, + radius: null, + yawLimitMin: null, + yawLimitMax: null, + audioListenerType: CameraPreset::AUDIO_LISTENER_TYPE_PLAYER, + playerEffects: false, + aimAssist: null, + controlScheme: null + )); + self::register("third_person_front", new CameraPreset( + name: "minecraft:third_person_front", + parent: "", + xPosition: null, + yPosition: null, + zPosition: null, + pitch: null, + yaw: null, + rotationSpeed: null, + snapToTarget: null, + horizontalRotationLimit: null, + verticalRotationLimit: null, + continueTargeting: null, + blockListeningRadius: null, + viewOffset: null, + entityOffset: null, + radius: null, + yawLimitMin: null, + yawLimitMax: null, + audioListenerType: CameraPreset::AUDIO_LISTENER_TYPE_PLAYER, + playerEffects: false, + aimAssist: null, + controlScheme: null + )); + + self::register("target", new CameraPreset( + name: "minecraft:target", + parent: "minecraft:free", + xPosition: null, + yPosition: null, + zPosition: null, + pitch: null, + yaw: null, + rotationSpeed: 0.0, + snapToTarget: true, + horizontalRotationLimit: new Vector2(0.0, 360.0), + verticalRotationLimit: new Vector2(0.0, 180.0), + continueTargeting: true, + blockListeningRadius: 50.0, + viewOffset: null, + entityOffset: null, + radius: null, + yawLimitMin: null, + yawLimitMax: null, + audioListenerType: CameraPreset::AUDIO_LISTENER_TYPE_CAMERA, + playerEffects: false, + aimAssist: null, + controlScheme: null + )); } } \ No newline at end of file diff --git a/src/muqsit/libcamera/libcamera.php b/src/muqsit/libcamera/libcamera.php index 841cfa3..73cbe7e 100644 --- a/src/muqsit/libcamera/libcamera.php +++ b/src/muqsit/libcamera/libcamera.php @@ -23,46 +23,57 @@ final class libcamera{ private static bool $registered = false; - private static CameraPresetRegistry $preset_registry; public static function isRegistered(): bool{ return self::$registered; } + protected static int $idCounter = 0; + /** @var array */ + public static array $network_ids; + private static ?CameraPresetsPacket $packetCache = null; + + private static function newId() : int{ + return self::$idCounter++; + } + + public static function registerPreset(CameraPreset $preset) : void{ + self::$network_ids[spl_object_id($preset)] = [$preset, self::newId()]; + self::$packetCache = null; + } + + private static function updateCache() : void{ + self::$packetCache ??= CameraPresetsPacket::create(array_values(array_column(self::$network_ids, 0))); + } + public static function register(Plugin $plugin) : void{ !self::$registered || throw new BadMethodCallException("Tried to registered an already existing libcamera instance"); - $preset_registry = new CameraPresetRegistry([ - "free" => new CameraPreset("minecraft:free", "", null, null, null, null, null, null, null, null, null, null, null, null, null, null, CameraPreset::AUDIO_LISTENER_TYPE_CAMERA, false, false, null), - "first_person" => new CameraPreset("minecraft:first_person", "", null, null, null, null, null, null, null, null, null, null, null, null, null, null, CameraPreset::AUDIO_LISTENER_TYPE_PLAYER, false, false, null), - "third_person" => new CameraPreset("minecraft:third_person", "", null, null, null, null, null, null, null, null, null, null, null, null, null, null, CameraPreset::AUDIO_LISTENER_TYPE_PLAYER, false, false, null), - "third_person_front" => new CameraPreset("minecraft:third_person_front", "", null, null, null, null, null, null, null, null, null, null, null, null, null, null, CameraPreset::AUDIO_LISTENER_TYPE_PLAYER, false, false, null), - "target" => new CameraPreset("minecraft:target", "minecraft:free", null, null, null, null, null, 0.0, true, new Vector2(0.0, 360.0), new Vector2(0.0, 180.0), true, 50.0, null, null, null, CameraPreset::AUDIO_LISTENER_TYPE_CAMERA, false, false, null) - ]); - $packet = CameraPresetsPacket::create(array_values($preset_registry->registered)); - Server::getInstance()->getPluginManager()->registerEvent(DataPacketReceiveEvent::class, function(DataPacketReceiveEvent $event) use($packet) : void{ + + self::registerPreset(CameraPresetRegistry::FREE()); + self::registerPreset(CameraPresetRegistry::FIRST_PERSON()); + self::registerPreset(CameraPresetRegistry::THIRD_PERSON()); + self::registerPreset(CameraPresetRegistry::THIRD_PERSON_FRONT()); + self::registerPreset(CameraPresetRegistry::TARGET()); + self::updateCache(); + + Server::getInstance()->getPluginManager()->registerEvent(DataPacketReceiveEvent::class, function(DataPacketReceiveEvent $event) : void{ + self::updateCache(); if($event->getPacket() instanceof SetLocalPlayerAsInitializedPacket){ - $event->getOrigin()->sendDataPacket($packet); + $event->getOrigin()->sendDataPacket(self::$packetCache ?? throw new \LogicException("Packet cache is null")); } }, EventPriority::MONITOR, $plugin); - Server::getInstance()->getPluginManager()->registerEvent(DataPacketSendEvent::class, function(DataPacketSendEvent $event) use ($packet) : void{ + Server::getInstance()->getPluginManager()->registerEvent(DataPacketSendEvent::class, function(DataPacketSendEvent $event) : void{ foreach($event->getPackets() as $packet){ if($packet instanceof StartGamePacket){ $experiments = $packet->levelSettings->experiments->getExperiments(); - $experiments["focus_target_camera"] = true; - $experiments["third_person_cameras"] = true; - $experiments["cameras"] = true; + $experiments["experimental_creator_camera"] = true;//It seems to work without it. $packet->levelSettings->experiments = new Experiments($experiments, true); } } }, EventPriority::HIGHEST, $plugin); - self::$preset_registry = $preset_registry; self::$registered = true; } - public static function getPresetRegistry() : CameraPresetRegistry{ - return self::$preset_registry; - } - public static function parseEaseType(string $type) : int{ return match($type){ "linear" => CameraSetInstructionEaseType::LINEAR,