diff --git a/pumpkin-protocol/src/bedrock/server/actor_event.rs b/pumpkin-protocol/src/bedrock/server/actor_event.rs new file mode 100644 index 000000000..c6b825097 --- /dev/null +++ b/pumpkin-protocol/src/bedrock/server/actor_event.rs @@ -0,0 +1,89 @@ +use crate::serial::PacketWrite; +use std::io::{Error, Write}; + +use pumpkin_macros::packet; +use pumpkin_util::math::vector3::Vector3; + +use crate::codec::var_int::VarInt; + +#[derive(Debug, PacketWrite)] +#[packet(27)] +pub struct SActorEvent { + pub entity_runtime_id: VarInt, + pub event_type: ActorEventType, + pub event_data: VarInt, + pub fire_at_position: Option>, +} + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum ActorEventType { + Jump = 1, + Hurt = 2, + Death = 3, + StartAttacking = 4, + StopAttacking = 5, + TamingFailed = 6, + TamingSucceeded = 7, + ShakeWetness = 8, + UseItem = 9, + EatGrass = 10, + FishhookBubble = 11, + FishhookFishPosition = 12, + FishhookHookTime = 13, + FishhookTease = 14, + SquidFleeing = 15, + ZombieConverting = 16, + PlayAmbient = 17, + SpawnAlive = 18, + StartOfferFlower = 19, + StopOfferFlower = 20, + LoveHearts = 21, + VillagerAngry = 22, + VillagerHappy = 23, + WitchHatMagic = 24, + FireworksExplode = 25, + InLoveHearts = 26, + SilverfishMergeAnimation = 27, + GuardianAttackSound = 28, + DrinkPotion = 29, + ThrowPotion = 30, + CartWithPrimeTNT = 31, + PrimeCreeper = 32, + AirSupply = 33, + AddPlayerLevels = 34, + GuardianMiningFatigue = 35, + AgentSwingArm = 36, + DragonStartDeathAnim = 37, + GroundDust = 38, + Shake = 39, + Feed = 57, + BabyEat = 60, + InstantDeath = 61, + NotifyTrade = 62, + LeashDestroyed = 63, + CaravanUpdated = 64, + TalismanActivate = 65, + UpdateStructureFeature = 66, + PlayerSpawnedMob = 67, + Puke = 68, + UpdateStackSize = 69, + StartSwimming = 70, + BalloonPop = 71, + TreasureHunt = 72, + SummonAgent = 73, + FinishedChargingItem = 74, + LandedOnGround = 75, + ActorGrowUp = 76, + VibrationDetected = 77, + DrinkMilk = 78, + WetnessStop = 79, + KineticDamageDealt = 80, + HurtWithoutReceivingDamage = 81, +} + +impl PacketWrite for ActorEventType { + fn write(&self, writer: &mut W) -> Result<(), Error> { + (*self as u8).write(writer) + } +} diff --git a/pumpkin-protocol/src/bedrock/server/interaction.rs b/pumpkin-protocol/src/bedrock/server/interaction.rs index b3b7fd1fe..4549a855e 100644 --- a/pumpkin-protocol/src/bedrock/server/interaction.rs +++ b/pumpkin-protocol/src/bedrock/server/interaction.rs @@ -19,6 +19,7 @@ pub struct SInteraction { pub enum Action { Invalid = 0, Interact = 1, + // No longer used in newer versions Attack = 2, StopRiding = 3, InteractUpdate = 4, diff --git a/pumpkin-protocol/src/bedrock/server/inventory_transaction.rs b/pumpkin-protocol/src/bedrock/server/inventory_transaction.rs new file mode 100644 index 000000000..671d04bac --- /dev/null +++ b/pumpkin-protocol/src/bedrock/server/inventory_transaction.rs @@ -0,0 +1,179 @@ +use std::io::{Error, ErrorKind, Read}; + +use pumpkin_macros::packet; +use pumpkin_util::math::position::BlockPos; + +use crate::{ + codec::{var_int::VarInt, var_uint::VarUInt, var_ulong::VarULong}, + serial::PacketRead, +}; + +pub const WINDOW_ID_INVENTORY: i32 = 0; +pub const WINDOW_ID_OFF_HAND: i32 = 119; +pub const WINDOW_ID_ARMOUR: i32 = 120; +pub const WINDOW_ID_UI: i32 = 124; + +#[derive(Debug, PartialEq, Eq)] +pub enum InventoryActionSource { + Container, + World, + Creative, + Todo, + Unknown(u32), +} + +impl From for InventoryActionSource { + fn from(value: u32) -> Self { + match value { + 0 => Self::Container, + 2 => Self::World, + 3 => Self::Creative, + 99999 => Self::Todo, + _ => Self::Unknown(value), + } + } +} + +#[derive(Debug)] +pub enum TransactionData { + Normal(NormalTransactionData), + Mismatch(MismatchTransactionData), + UseItem(UseItemTransactionData), + UseItemOnEntity(UseItemOnEntityTransactionData), + ReleaseItem(ReleaseItemTransactionData), +} + +#[derive(Debug, PacketRead)] +pub struct LegacySetItemSlot { + pub container_id: u8, + pub slots: Vec, +} + +#[derive(Debug)] +pub struct InventoryAction { + pub source_type: u32, + pub window_id: Option, + pub source_flags: Option, + pub inventory_slot: u32, + // TODO + pub old_item: (), + pub new_item: (), +} + +impl PacketRead for InventoryAction { + fn read(buf: &mut R) -> Result { + let source_type = VarULong::read(buf)?.0 as u32; + + let mut window_id = None; + let mut source_flags = None; + + match InventoryActionSource::from(source_type) { + InventoryActionSource::Container | InventoryActionSource::Todo => { + window_id = Some(VarInt::read(buf)?.0); + } + InventoryActionSource::World => { + source_flags = Some(VarULong::read(buf)?.0 as u32); + } + _ => {} + } + + let inventory_slot = VarULong::read(buf)?.0 as u32; + + // let old_item = ItemStack::read(buf)?; + // let new_item = ItemStack::read(buf)?; + + Ok(Self { + source_type, + window_id, + source_flags, + inventory_slot, + old_item: (), + new_item: (), + }) + } +} + +#[derive(Debug, PacketRead)] +pub struct NormalTransactionData; + +#[derive(Debug, PacketRead)] +pub struct MismatchTransactionData; + +#[derive(Debug, PacketRead)] +pub struct UseItemTransactionData { + pub action_type: VarULong, + pub trigger_type: VarULong, + pub block_position: BlockPos, + pub block_face: VarInt, + pub hot_bar_slot: VarInt, + // TODO +} + +#[derive(Debug, PacketRead)] +pub struct UseItemOnEntityTransactionData { + pub target_entity_runtime_id: VarULong, + pub action_type: VarULong, + pub hot_bar_slot: VarInt, + // TODO +} + +#[derive(Debug, PacketRead)] +pub struct ReleaseItemTransactionData { + pub action_type: VarULong, + pub hot_bar_slot: VarInt, + // TODO +} + +#[derive(Debug)] +#[packet(30)] +pub struct SInventoryTransaction { + pub legacy_request_id: VarInt, + pub legacy_set_item_slots: Vec, + pub actions: Vec, + pub transaction_type: VarUInt, + pub transaction_data: TransactionData, +} + +impl PacketRead for SInventoryTransaction { + fn read(buf: &mut R) -> Result { + let legacy_request_id = VarInt::read(buf)?; + + let mut legacy_set_item_slots = Vec::new(); + if legacy_request_id.0 != 0 { + let len = VarUInt::read(buf)?.0; + for _ in 0..len { + legacy_set_item_slots.push(LegacySetItemSlot::read(buf)?); + } + } + + let transaction_type = VarUInt::read(buf)?; + + let actions_len = VarUInt::read(buf)?.0; + let mut actions = Vec::new(); + for _ in 0..actions_len { + actions.push(InventoryAction::read(buf)?); + } + + let transaction_data = match transaction_type.0 { + 0 => TransactionData::Normal(NormalTransactionData::read(buf)?), + 1 => TransactionData::Mismatch(MismatchTransactionData::read(buf)?), + 2 => TransactionData::UseItem(UseItemTransactionData::read(buf)?), + 3 => TransactionData::UseItemOnEntity(UseItemOnEntityTransactionData::read(buf)?), + 4 => TransactionData::ReleaseItem(ReleaseItemTransactionData::read(buf)?), + _ => { + return Err(Error::new( + ErrorKind::InvalidData, + format!("Unknown inventory transaction type: {}", transaction_type.0), + )); + } + }; + + Ok(Self { + legacy_request_id, + legacy_set_item_slots, + actions, + transaction_type, + transaction_data, + }) + } +} diff --git a/pumpkin-protocol/src/bedrock/server/mod.rs b/pumpkin-protocol/src/bedrock/server/mod.rs index 21d2cf084..f74173988 100644 --- a/pumpkin-protocol/src/bedrock/server/mod.rs +++ b/pumpkin-protocol/src/bedrock/server/mod.rs @@ -1,8 +1,10 @@ +pub mod actor_event; pub mod animate; pub mod client_cache_status; pub mod command_request; pub mod container_close; pub mod interaction; +pub mod inventory_transaction; pub mod loading_screen; pub mod login; pub mod modal_form_response; diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index a4a2d4faf..a7cccffad 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -6,6 +6,7 @@ use pumpkin_data::tracked_data::{TrackedData, TrackedId}; use pumpkin_inventory::build_equipment_slots; use pumpkin_inventory::player::player_inventory::PlayerInventory; use pumpkin_inventory::screen_handler::InventoryPlayer; +use pumpkin_protocol::bedrock::server::actor_event::{ActorEventType, SActorEvent}; use pumpkin_util::GameMode; use pumpkin_util::Hand; use pumpkin_util::math::position::BlockPos; @@ -2025,7 +2026,15 @@ impl EntityBase for LivingEntity { (src.z - tgt.z).atan2(src.x - tgt.x).to_degrees() as f32 - self.entity.yaw.load() }); - world.broadcast_packet_all(&CHurtAnimation::new(entity_id, hurt_yaw)); + let hurt_event = SActorEvent { + entity_runtime_id: entity_id, + event_type: ActorEventType::Hurt, + event_data: VarInt(0), + fire_at_position: None, + }; + world + .broadcast_editioned(&CHurtAnimation::new(entity_id, hurt_yaw), &hurt_event) + .await; } world.broadcast_packet_all(&CDamageEvent::new( diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 44eace0b7..aa9112b55 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -32,6 +32,7 @@ use pumpkin_data::{ sound::{Sound, SoundCategory}, }; use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag}; +use pumpkin_protocol::bedrock::client::CSetActorMotion; use pumpkin_protocol::java::client::play::{CUpdateEntityPos, CUpdateEntityPosRot}; use pumpkin_protocol::{ PositionFlag, @@ -701,9 +702,14 @@ impl Entity { pub fn send_velocity(&self) { let velocity = self.velocity.load(); let chunk_pos = self.chunk_pos.load(); - self.world.load().broadcast_to_chunk( + self.world.load().broadcast_to_chunk_editioned_sync( chunk_pos, &CEntityVelocity::new(self.entity_id.into(), velocity), + &CSetActorMotion::new( + VarULong(self.entity_id as u64), + Vector3::new(velocity.x as f32, velocity.y as f32, velocity.z as f32), + VarULong(0), + ), ); } diff --git a/pumpkin/src/net/bedrock/mod.rs b/pumpkin/src/net/bedrock/mod.rs index 647daf612..22899b839 100644 --- a/pumpkin/src/net/bedrock/mod.rs +++ b/pumpkin/src/net/bedrock/mod.rs @@ -32,6 +32,7 @@ use pumpkin_protocol::{ command_request::SCommandRequest, container_close::SContainerClose, interaction::SInteraction, + inventory_transaction::SInventoryTransaction, loading_screen::SLoadingScreen, login::SLogin, player_action::SPlayerAction, @@ -906,6 +907,9 @@ impl BedrockClient { self.handle_request_chunk_radius(player, SRequestChunkRadius::read(reader)?) .await; } + SInventoryTransaction::PACKET_ID => { + self.handle_inventory_action(player, SInventoryTransaction::read(reader)?).await; + } SInteraction::PACKET_ID => { self.handle_interaction(player, SInteraction::read(reader)?) .await; diff --git a/pumpkin/src/net/bedrock/play.rs b/pumpkin/src/net/bedrock/play.rs index 1d8e5ea4f..c3e97130e 100644 --- a/pumpkin/src/net/bedrock/play.rs +++ b/pumpkin/src/net/bedrock/play.rs @@ -12,6 +12,7 @@ use pumpkin_protocol::{ command_request::SCommandRequest, container_close::SContainerClose, interaction::{Action, SInteraction}, + inventory_transaction::{SInventoryTransaction, TransactionData}, player_action::{Action as PlayerAction, SPlayerAction}, player_auth_input::{InputData, SPlayerAuthInput}, request_chunk_radius::SRequestChunkRadius, @@ -337,6 +338,51 @@ impl BedrockClient { } } + pub async fn handle_inventory_action( + &self, + player: &Arc, + packet: SInventoryTransaction, + ) { + match packet.transaction_data { + TransactionData::Normal(_data) => { + // TODO + } + TransactionData::Mismatch(_data) => { + // TODO + } + TransactionData::UseItem(_data) => { + // TODO + } + TransactionData::UseItemOnEntity(data) => { + let target_runtime_id = data.target_entity_runtime_id.0 as i32; + // TODO: replace with consts, i'm too lazy + match data.action_type.0 { + // Interact + 0 => { + // TODO + } + // Attack + 1 => { + let world = player.world(); + if let Some(target) = world.get_entity_by_id(target_runtime_id) { + player.attack(target).await; + } + } + _ => { + tracing::warn!( + "invalid UseItemOnEntity action type {}", + data.action_type.0 + ); + // Kick? + } + } + } + TransactionData::ReleaseItem(_data) => { + // TODO + } + } + } + pub async fn handle_interaction(&self, player: &Arc, packet: SInteraction) { match packet.action { Action::OpenInventory => { @@ -348,6 +394,7 @@ impl BedrockClient { }) .await; } + // No longer used in newer versions Action::Attack => { let target_runtime_id = packet.target_runtime_id.0 as i32; let world = player.world();