feat: set camera for spectators
This commit is contained in:
parent
65fe9f0af0
commit
0c1cb25c05
@ -916,6 +916,8 @@ impl DataComponentCodec<Self> for UseCooldownImpl {
|
|||||||
fn deserialize_item_stack_template<'a, A: SeqAccess<'a>>(
|
fn deserialize_item_stack_template<'a, A: SeqAccess<'a>>(
|
||||||
seq: &mut A,
|
seq: &mut A,
|
||||||
) -> Result<pumpkin_data::item_stack::ItemStack, A::Error> {
|
) -> Result<pumpkin_data::item_stack::ItemStack, A::Error> {
|
||||||
|
const MAX_COMPONENTS: i32 = 256;
|
||||||
|
|
||||||
let item_id = seq
|
let item_id = seq
|
||||||
.next_element::<VarInt>()?
|
.next_element::<VarInt>()?
|
||||||
.ok_or_else(|| de::Error::custom("Missing item_id in ItemStackTemplate"))?
|
.ok_or_else(|| de::Error::custom("Missing item_id in ItemStackTemplate"))?
|
||||||
@ -933,7 +935,6 @@ fn deserialize_item_stack_template<'a, A: SeqAccess<'a>>(
|
|||||||
return Err(de::Error::custom("Negative component count"));
|
return Err(de::Error::custom("Negative component count"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_COMPONENTS: i32 = 256;
|
|
||||||
let total_components = num_to_add
|
let total_components = num_to_add
|
||||||
.checked_add(num_to_remove)
|
.checked_add(num_to_remove)
|
||||||
.ok_or_else(|| de::Error::custom("Component count overflow"))?;
|
.ok_or_else(|| de::Error::custom("Component count overflow"))?;
|
||||||
|
|||||||
@ -68,6 +68,7 @@ mod set_border_lerp_size;
|
|||||||
mod set_border_size;
|
mod set_border_size;
|
||||||
mod set_border_warning_delay;
|
mod set_border_warning_delay;
|
||||||
mod set_border_warning_distance;
|
mod set_border_warning_distance;
|
||||||
|
mod set_camera;
|
||||||
mod set_container_content;
|
mod set_container_content;
|
||||||
mod set_container_property;
|
mod set_container_property;
|
||||||
mod set_container_slot;
|
mod set_container_slot;
|
||||||
@ -173,6 +174,7 @@ pub use set_border_lerp_size::*;
|
|||||||
pub use set_border_size::*;
|
pub use set_border_size::*;
|
||||||
pub use set_border_warning_delay::*;
|
pub use set_border_warning_delay::*;
|
||||||
pub use set_border_warning_distance::*;
|
pub use set_border_warning_distance::*;
|
||||||
|
pub use set_camera::*;
|
||||||
pub use set_container_content::*;
|
pub use set_container_content::*;
|
||||||
pub use set_container_property::*;
|
pub use set_container_property::*;
|
||||||
pub use set_container_slot::*;
|
pub use set_container_slot::*;
|
||||||
|
|||||||
23
pumpkin-protocol/src/java/client/play/set_camera.rs
Normal file
23
pumpkin-protocol/src/java/client/play/set_camera.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use pumpkin_data::packet::clientbound::PLAY_SET_CAMERA;
|
||||||
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{VarInt, packet::MultiVersionJavaPacket};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct CSetCamera {
|
||||||
|
pub camera_id: VarInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CSetCamera {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(camera_id: VarInt) -> Self {
|
||||||
|
Self { camera_id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MultiVersionJavaPacket for CSetCamera {
|
||||||
|
fn to_id(version: JavaMinecraftVersion) -> i32 {
|
||||||
|
PLAY_SET_CAMERA.to_id(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -43,6 +43,7 @@ mod set_creative_slot;
|
|||||||
mod set_held_item;
|
mod set_held_item;
|
||||||
mod set_jigsaw_block;
|
mod set_jigsaw_block;
|
||||||
mod swing_arm;
|
mod swing_arm;
|
||||||
|
mod teleport_to_entity;
|
||||||
mod update_sign;
|
mod update_sign;
|
||||||
mod use_item;
|
mod use_item;
|
||||||
mod use_item_on;
|
mod use_item_on;
|
||||||
@ -92,6 +93,13 @@ pub use set_creative_slot::*;
|
|||||||
pub use set_held_item::*;
|
pub use set_held_item::*;
|
||||||
pub use set_jigsaw_block::*;
|
pub use set_jigsaw_block::*;
|
||||||
pub use swing_arm::*;
|
pub use swing_arm::*;
|
||||||
|
pub use teleport_to_entity::*;
|
||||||
pub use update_sign::*;
|
pub use update_sign::*;
|
||||||
pub use use_item::*;
|
pub use use_item::*;
|
||||||
pub use use_item_on::*;
|
pub use use_item_on::*;
|
||||||
|
|
||||||
|
mod test_instance_block_action;
|
||||||
|
pub use test_instance_block_action::*;
|
||||||
|
|
||||||
|
mod set_test_block;
|
||||||
|
pub use set_test_block::*;
|
||||||
|
|||||||
19
pumpkin-protocol/src/java/server/play/set_test_block.rs
Normal file
19
pumpkin-protocol/src/java/server/play/set_test_block.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use pumpkin_data::packet::serverbound::PLAY_SET_TEST_BLOCK;
|
||||||
|
use pumpkin_macros::java_packet;
|
||||||
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[java_packet(PLAY_SET_TEST_BLOCK)]
|
||||||
|
pub struct SSetTestBlock {
|
||||||
|
pub position: BlockPos,
|
||||||
|
pub mode: TestBlockMode,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum TestBlockMode {
|
||||||
|
Start,
|
||||||
|
Log,
|
||||||
|
Fail,
|
||||||
|
Accept,
|
||||||
|
}
|
||||||
10
pumpkin-protocol/src/java/server/play/teleport_to_entity.rs
Normal file
10
pumpkin-protocol/src/java/server/play/teleport_to_entity.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use pumpkin_data::packet::serverbound::PLAY_TELEPORT_TO_ENTITY;
|
||||||
|
use pumpkin_macros::java_packet;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[java_packet(PLAY_TELEPORT_TO_ENTITY)]
|
||||||
|
pub struct STeleportToEntity {
|
||||||
|
#[serde(with = "uuid::serde::compact")]
|
||||||
|
pub target: uuid::Uuid,
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
use pumpkin_data::packet::serverbound::PLAY_TEST_INSTANCE_BLOCK_ACTION;
|
||||||
|
use pumpkin_macros::java_packet;
|
||||||
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
|
||||||
|
use crate::VarInt;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[java_packet(PLAY_TEST_INSTANCE_BLOCK_ACTION)]
|
||||||
|
pub struct STestInstanceBlockAction {
|
||||||
|
pub pos: BlockPos,
|
||||||
|
pub action: TestInstanceBlockAction,
|
||||||
|
pub data: TestInstanceBlockData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum TestInstanceBlockAction {
|
||||||
|
Init,
|
||||||
|
Query,
|
||||||
|
Set,
|
||||||
|
Reset,
|
||||||
|
Save,
|
||||||
|
Export,
|
||||||
|
Run,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct VarIntVector3 {
|
||||||
|
pub x: VarInt,
|
||||||
|
pub y: VarInt,
|
||||||
|
pub z: VarInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct TestInstanceBlockData {
|
||||||
|
pub test: Option<String>,
|
||||||
|
pub size: VarIntVector3,
|
||||||
|
pub rotation: pumpkin_data::block_rotation::Rotation,
|
||||||
|
pub ignore_entities: bool,
|
||||||
|
pub status: TestInstanceBlockStatus,
|
||||||
|
pub error_message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub enum TestInstanceBlockStatus {
|
||||||
|
Cleared,
|
||||||
|
Running,
|
||||||
|
Success,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
@ -67,9 +67,9 @@ use pumpkin_protocol::java::client::play::{
|
|||||||
CCloseContainer, CCombatDeath, CCustomPayload, CDisguisedChatMessage, CEntityAnimation,
|
CCloseContainer, CCombatDeath, CCustomPayload, CDisguisedChatMessage, CEntityAnimation,
|
||||||
CEntityPositionSync, CGameEvent, CItemCooldown, CMapItemData, COpenScreen, CParticle,
|
CEntityPositionSync, CGameEvent, CItemCooldown, CMapItemData, COpenScreen, CParticle,
|
||||||
CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, CPlayerSpawnPosition, CRespawn,
|
CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, CPlayerSpawnPosition, CRespawn,
|
||||||
CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem, CSetEquipment,
|
CSetCamera, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem,
|
||||||
CSetExperience, CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound,
|
CSetEquipment, CSetExperience, CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect,
|
||||||
CSubtitle, CSystemChatMessage, CTabList, CTitleAnimation, CTitleText, CUnloadChunk,
|
CStopSound, CSubtitle, CSystemChatMessage, CTabList, CTitleAnimation, CTitleText, CUnloadChunk,
|
||||||
CUpdateMobEffect, CUpdateTime, GameEvent, MapIcon, MapPatch, Metadata, PlayerAction,
|
CUpdateMobEffect, CUpdateTime, GameEvent, MapIcon, MapPatch, Metadata, PlayerAction,
|
||||||
PlayerInfoFlags, PlayerSpawnData, PreviousMessage, Statistic,
|
PlayerInfoFlags, PlayerSpawnData, PreviousMessage, Statistic,
|
||||||
};
|
};
|
||||||
@ -422,6 +422,8 @@ pub struct Player {
|
|||||||
pub gamemode: AtomicCell<GameMode>,
|
pub gamemode: AtomicCell<GameMode>,
|
||||||
/// The player's previous gamemode
|
/// The player's previous gamemode
|
||||||
pub previous_gamemode: AtomicCell<Option<GameMode>>,
|
pub previous_gamemode: AtomicCell<Option<GameMode>>,
|
||||||
|
/// The entity ID of the entity that the player is currently spectating/camera targeting.
|
||||||
|
pub camera_target_id: AtomicCell<Option<i32>>,
|
||||||
/// The player's spawnpoint
|
/// The player's spawnpoint
|
||||||
pub respawn_point: Mutex<Option<RespawnPoint>>,
|
pub respawn_point: Mutex<Option<RespawnPoint>>,
|
||||||
/// The player's sleep status
|
/// The player's sleep status
|
||||||
@ -656,6 +658,7 @@ impl Player {
|
|||||||
stats: Mutex::new(statistics::Statistics::default()),
|
stats: Mutex::new(statistics::Statistics::default()),
|
||||||
gamemode: AtomicCell::new(gamemode),
|
gamemode: AtomicCell::new(gamemode),
|
||||||
previous_gamemode: AtomicCell::new(None),
|
previous_gamemode: AtomicCell::new(None),
|
||||||
|
camera_target_id: AtomicCell::new(None),
|
||||||
// TODO: Send the CPlayerSpawnPosition packet when the client connects with proper values
|
// TODO: Send the CPlayerSpawnPosition packet when the client connects with proper values
|
||||||
respawn_point: Mutex::new(None),
|
respawn_point: Mutex::new(None),
|
||||||
sleeping_since: AtomicCell::new(None),
|
sleeping_since: AtomicCell::new(None),
|
||||||
@ -1859,6 +1862,32 @@ impl Player {
|
|||||||
// TODO Abstract the chunk sending
|
// TODO Abstract the chunk sending
|
||||||
#[expect(clippy::too_many_lines)]
|
#[expect(clippy::too_many_lines)]
|
||||||
pub async fn tick(self: &Arc<Self>, server: &Server) {
|
pub async fn tick(self: &Arc<Self>, server: &Server) {
|
||||||
|
if let Some(camera_id) = self.camera_target_id.load() {
|
||||||
|
if camera_id == self.entity_id() {
|
||||||
|
self.camera_target_id.store(None);
|
||||||
|
} else {
|
||||||
|
let world = self.world();
|
||||||
|
let target = world
|
||||||
|
.get_player_by_id(camera_id)
|
||||||
|
.map(|p| Arc::clone(&p) as Arc<dyn EntityBase>)
|
||||||
|
.or_else(|| world.get_entity_by_id(camera_id));
|
||||||
|
if let Some(target) = target {
|
||||||
|
let target_pos = target.get_entity().pos.load();
|
||||||
|
let player_pos = self.living_entity.entity.pos.load();
|
||||||
|
if player_pos != target_pos {
|
||||||
|
self.living_entity.entity.set_pos(target_pos);
|
||||||
|
crate::world::chunker::update_position(self).await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Target no longer exists, reset camera back to player
|
||||||
|
self.camera_target_id.store(None);
|
||||||
|
self.client
|
||||||
|
.send_packet_now(&CSetCamera::new(self.entity_id().into()))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.current_screen_handler
|
self.current_screen_handler
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
@ -2855,6 +2884,13 @@ impl Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if gamemode != GameMode::Spectator && self.camera_target_id.load().is_some() {
|
||||||
|
self.camera_target_id.store(None);
|
||||||
|
self.client.send_packet_now(&CSetCamera::new(
|
||||||
|
self.entity_id().into()
|
||||||
|
)).await;
|
||||||
|
}
|
||||||
|
|
||||||
self.living_entity.entity.invulnerable.store(
|
self.living_entity.entity.invulnerable.store(
|
||||||
matches!(gamemode, GameMode::Creative | GameMode::Spectator),
|
matches!(gamemode, GameMode::Creative | GameMode::Spectator),
|
||||||
Ordering::Relaxed,
|
Ordering::Relaxed,
|
||||||
|
|||||||
@ -31,20 +31,19 @@ impl ItemBehaviour for BundleItem {
|
|||||||
matched = true;
|
matched = true;
|
||||||
if let Some(bundle_contents) =
|
if let Some(bundle_contents) =
|
||||||
held_item.get_data_component_mut::<BundleContentsImpl>()
|
held_item.get_data_component_mut::<BundleContentsImpl>()
|
||||||
|
&& let Some(extracted_stack) = bundle_contents.try_extract()
|
||||||
{
|
{
|
||||||
if let Some(extracted_stack) = bundle_contents.try_extract() {
|
let position = player.position();
|
||||||
let position = player.position();
|
player.world().play_sound(
|
||||||
player.world().play_sound(
|
Sound::ItemBundleRemoveOne,
|
||||||
Sound::ItemBundleRemoveOne,
|
pumpkin_data::sound::SoundCategory::Players,
|
||||||
pumpkin_data::sound::SoundCategory::Players,
|
&position,
|
||||||
&position,
|
);
|
||||||
);
|
let updated_bundle = held_item.clone();
|
||||||
let updated_bundle = held_item.clone();
|
drop(held_item);
|
||||||
drop(held_item);
|
|
||||||
|
|
||||||
player.drop_item(extracted_stack).await;
|
player.drop_item(extracted_stack).await;
|
||||||
player.sync_hand_slot(used_slot_index, updated_bundle).await;
|
player.sync_hand_slot(used_slot_index, updated_bundle).await;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,20 +54,19 @@ impl ItemBehaviour for BundleItem {
|
|||||||
used_slot_index = 40; // OFF_HAND_SLOT
|
used_slot_index = 40; // OFF_HAND_SLOT
|
||||||
if let Some(bundle_contents) =
|
if let Some(bundle_contents) =
|
||||||
off_hand_item.get_data_component_mut::<BundleContentsImpl>()
|
off_hand_item.get_data_component_mut::<BundleContentsImpl>()
|
||||||
|
&& let Some(extracted_stack) = bundle_contents.try_extract()
|
||||||
{
|
{
|
||||||
if let Some(extracted_stack) = bundle_contents.try_extract() {
|
let position = player.position();
|
||||||
let position = player.position();
|
player.world().play_sound(
|
||||||
player.world().play_sound(
|
Sound::ItemBundleRemoveOne,
|
||||||
Sound::ItemBundleRemoveOne,
|
pumpkin_data::sound::SoundCategory::Players,
|
||||||
pumpkin_data::sound::SoundCategory::Players,
|
&position,
|
||||||
&position,
|
);
|
||||||
);
|
let updated_bundle = off_hand_item.clone();
|
||||||
let updated_bundle = off_hand_item.clone();
|
drop(off_hand_item);
|
||||||
drop(off_hand_item);
|
|
||||||
|
|
||||||
player.drop_item(extracted_stack).await;
|
player.drop_item(extracted_stack).await;
|
||||||
player.sync_hand_slot(used_slot_index, updated_bundle).await;
|
player.sync_hand_slot(used_slot_index, updated_bundle).await;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,8 @@ use pumpkin_protocol::java::server::play::{
|
|||||||
SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition,
|
SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition,
|
||||||
SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings,
|
SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings,
|
||||||
SRecipeBookSeenRecipe, SRenameItem, SSelectTrade, SSetCommandBlock, SSetCreativeSlot,
|
SRecipeBookSeenRecipe, SRenameItem, SSelectTrade, SSetCommandBlock, SSetCreativeSlot,
|
||||||
SSetHeldItem, SSetJigsawBlock, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem, SUseItemOn,
|
SSetHeldItem, SSetJigsawBlock, SSetPlayerGround, SSetTestBlock, SSwingArm, STeleportToEntity,
|
||||||
|
STestInstanceBlockAction, SUpdateSign, SUseItem, SUseItemOn,
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::packet::MultiVersionJavaPacket;
|
use pumpkin_protocol::packet::MultiVersionJavaPacket;
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
@ -923,6 +924,14 @@ impl JavaClient {
|
|||||||
self.handle_attack(player, SAttack::read(payload, &version)?, server)
|
self.handle_attack(player, SAttack::read(payload, &version)?, server)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
id if id == STeleportToEntity::to_id(version) => {
|
||||||
|
self.handle_teleport_to_entity(
|
||||||
|
player,
|
||||||
|
STeleportToEntity::read(payload, &version)?,
|
||||||
|
server,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
id if id == pumpkin_protocol::java::server::play::SKeepAlive::to_id(version) => {
|
id if id == pumpkin_protocol::java::server::play::SKeepAlive::to_id(version) => {
|
||||||
self.handle_keep_alive(
|
self.handle_keep_alive(
|
||||||
player,
|
player,
|
||||||
@ -933,6 +942,15 @@ impl JavaClient {
|
|||||||
id if id == SClientTickEnd::to_id(version) => {
|
id if id == SClientTickEnd::to_id(version) => {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
id if id == STestInstanceBlockAction::to_id(version) => {
|
||||||
|
self.handle_test_instance_block_action(
|
||||||
|
player,
|
||||||
|
&STestInstanceBlockAction::read(payload, &version)?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
id if id == SSetTestBlock::to_id(version) => {
|
||||||
|
self.handle_set_test_block(player, &SSetTestBlock::read(payload, &version)?);
|
||||||
|
}
|
||||||
id if id == SPlayerPosition::to_id(version) => {
|
id if id == SPlayerPosition::to_id(version) => {
|
||||||
self.handle_position(player, server, SPlayerPosition::read(payload, &version)?)
|
self.handle_position(player, server, SPlayerPosition::read(payload, &version)?)
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@ -61,8 +61,9 @@ use pumpkin_protocol::codec::var_int::VarInt;
|
|||||||
use pumpkin_protocol::codec::var_ulong::VarULong;
|
use pumpkin_protocol::codec::var_ulong::VarULong;
|
||||||
use pumpkin_protocol::java::client::play::{
|
use pumpkin_protocol::java::client::play::{
|
||||||
CBlockUpdate, CCommandSuggestions, CEntityPositionSync, CHeadRot, COpenSignEditor,
|
CBlockUpdate, CCommandSuggestions, CEntityPositionSync, CHeadRot, COpenSignEditor,
|
||||||
CPingResponse, CPlayerInfoUpdate, CPlayerPosition, CSetSelectedSlot, CSystemChatMessage,
|
CPingResponse, CPlayerInfoUpdate, CPlayerPosition, CSetCamera, CSetSelectedSlot,
|
||||||
CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, InitChat, PlayerAction,
|
CSystemChatMessage, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, InitChat,
|
||||||
|
PlayerAction,
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::java::server::play::{
|
use pumpkin_protocol::java::server::play::{
|
||||||
Action, ActionType, CommandBlockMode, FLAG_ON_GROUND, SAttack, SBundleItemSelected,
|
Action, ActionType, CommandBlockMode, FLAG_ON_GROUND, SAttack, SBundleItemSelected,
|
||||||
@ -73,7 +74,8 @@ use pumpkin_protocol::java::server::play::{
|
|||||||
SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerPosition, SPlayerPositionRotation,
|
SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerPosition, SPlayerPositionRotation,
|
||||||
SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings, SRecipeBookSeenRecipe,
|
SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings, SRecipeBookSeenRecipe,
|
||||||
SSelectTrade, SSetCommandBlock, SSetCreativeSlot, SSetHeldItem, SSetJigsawBlock,
|
SSelectTrade, SSetCommandBlock, SSetCreativeSlot, SSetHeldItem, SSetJigsawBlock,
|
||||||
SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem, SUseItemOn, Status,
|
SSetPlayerGround, SSetTestBlock, SSwingArm, STeleportToEntity, STestInstanceBlockAction,
|
||||||
|
SUpdateSign, SUseItem, SUseItemOn, Status,
|
||||||
};
|
};
|
||||||
use pumpkin_util::math::boundingbox::BoundingBox;
|
use pumpkin_util::math::boundingbox::BoundingBox;
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
@ -987,6 +989,17 @@ impl JavaClient {
|
|||||||
player.last_input.store(input.input, Ordering::Relaxed);
|
player.last_input.store(input.input, Ordering::Relaxed);
|
||||||
|
|
||||||
let sneak = input.input & SPlayerInput::SNEAK != 0;
|
let sneak = input.input & SPlayerInput::SNEAK != 0;
|
||||||
|
if sneak
|
||||||
|
&& player.gamemode.load() == GameMode::Spectator
|
||||||
|
&& player.camera_target_id.load().is_some()
|
||||||
|
{
|
||||||
|
player.camera_target_id.store(None);
|
||||||
|
player
|
||||||
|
.client
|
||||||
|
.send_packet_now(&CSetCamera::new(player.entity_id().into()))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
if player.get_entity().is_sneaking() != sneak {
|
if player.get_entity().is_sneaking() != sneak {
|
||||||
send_cancellable! {{
|
send_cancellable! {{
|
||||||
server;
|
server;
|
||||||
@ -1699,6 +1712,7 @@ impl JavaClient {
|
|||||||
player.attack(target).await;
|
player.attack(target).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
pub async fn handle_interact(
|
pub async fn handle_interact(
|
||||||
&self,
|
&self,
|
||||||
player: &Arc<Player>,
|
player: &Arc<Player>,
|
||||||
@ -1730,6 +1744,14 @@ impl JavaClient {
|
|||||||
.or_else(|| world.get_entity_by_id(entity_id.0));
|
.or_else(|| world.get_entity_by_id(entity_id.0));
|
||||||
|
|
||||||
if let Some(target) = target {
|
if let Some(target) = target {
|
||||||
|
if player.gamemode.load() == GameMode::Spectator {
|
||||||
|
player.camera_target_id.store(Some(entity_id.0));
|
||||||
|
player
|
||||||
|
.client
|
||||||
|
.send_packet_now(&CSetCamera::new(entity_id))
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
send_cancellable! {{
|
send_cancellable! {{
|
||||||
server;
|
server;
|
||||||
PlayerInteractEntityEvent::new(
|
PlayerInteractEntityEvent::new(
|
||||||
@ -2942,4 +2964,63 @@ impl JavaClient {
|
|||||||
packet.slot_id.0, selected_item_index
|
packet.slot_id.0, selected_item_index
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_teleport_to_entity(
|
||||||
|
&self,
|
||||||
|
player: &Arc<Player>,
|
||||||
|
packet: STeleportToEntity,
|
||||||
|
server: &Server,
|
||||||
|
) {
|
||||||
|
if !player.has_client_loaded() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.update_last_action_time();
|
||||||
|
|
||||||
|
if player.gamemode.load() != GameMode::Spectator {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(target_player) = server.get_player_by_uuid(packet.target) {
|
||||||
|
let target_pos = target_player.living_entity.entity.pos.load();
|
||||||
|
let target_yaw = target_player.living_entity.entity.yaw.load();
|
||||||
|
let target_pitch = target_player.living_entity.entity.pitch.load();
|
||||||
|
|
||||||
|
let target_id = target_player.living_entity.entity.entity_id;
|
||||||
|
player.camera_target_id.store(Some(target_id));
|
||||||
|
player
|
||||||
|
.client
|
||||||
|
.send_packet_now(&CSetCamera::new(target_id.into()))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
player
|
||||||
|
.request_teleport(target_pos, target_yaw, target_pitch)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_test_instance_block_action(
|
||||||
|
&self,
|
||||||
|
player: &Arc<Player>,
|
||||||
|
packet: &STestInstanceBlockAction,
|
||||||
|
) {
|
||||||
|
if !player.has_client_loaded() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.update_last_action_time();
|
||||||
|
debug!(
|
||||||
|
"Test instance block action at {:?}: action={:?}",
|
||||||
|
packet.pos, packet.action
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_set_test_block(&self, player: &Arc<Player>, packet: &SSetTestBlock) {
|
||||||
|
if !player.has_client_loaded() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.update_last_action_time();
|
||||||
|
debug!(
|
||||||
|
"Set test block at {:?}: mode={:?}, message={}",
|
||||||
|
packet.position, packet.mode, packet.message
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user