From d33484d05754689a1184ea1414b4f3022839a43f Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sat, 2 May 2026 14:06:49 +0200 Subject: [PATCH] fix: ender pearl delay --- pumpkin-data/src/data_component_impl.rs | 60 ++++++++++++++++- pumpkin-data/src/item_stack/mod.rs | 9 ++- pumpkin-protocol/src/bedrock/server/mod.rs | 1 + .../server/set_local_player_as_initialized.rs | 16 +++++ pumpkin-protocol/src/codec/data_component.rs | 27 +++++++- .../src/java/client/play/item_cooldown.rs | 19 ++++++ pumpkin-protocol/src/java/client/play/mod.rs | 2 + pumpkin-world/src/lighting/runtime.rs | 24 ++++--- pumpkin/src/entity/living.rs | 10 +++ pumpkin/src/entity/mob/bat.rs | 8 +-- pumpkin/src/entity/mob/creeper.rs | 8 +-- pumpkin/src/entity/mob/shulker.rs | 8 +-- pumpkin/src/entity/mob/slime.rs | 11 +--- pumpkin/src/entity/mod.rs | 2 +- pumpkin/src/entity/passive/sheep.rs | 8 +-- pumpkin/src/entity/player.rs | 64 ++++++++++++++++++- pumpkin/src/item/registry.rs | 50 ++++++++++++++- pumpkin/src/net/bedrock/mod.rs | 15 +++-- pumpkin/src/net/bedrock/play.rs | 26 ++++++-- pumpkin/src/net/java/play.rs | 17 ++++- pumpkin/src/world/mod.rs | 4 -- 21 files changed, 312 insertions(+), 77 deletions(-) create mode 100644 pumpkin-protocol/src/bedrock/server/set_local_player_as_initialized.rs create mode 100644 pumpkin-protocol/src/java/client/play/item_cooldown.rs diff --git a/pumpkin-data/src/data_component_impl.rs b/pumpkin-data/src/data_component_impl.rs index b3e7a5109..208492cfe 100644 --- a/pumpkin-data/src/data_component_impl.rs +++ b/pumpkin-data/src/data_component_impl.rs @@ -6,7 +6,7 @@ use crate::data_component::DataComponent::{ AttributeModifiers, BlocksAttacks, Consumable, CustomData, CustomName, Damage, DamageResistant, DeathProtection, Enchantments, Equippable, FireworkExplosion, Fireworks, Food, ItemModel, ItemName, JukeboxPlayable, MaxDamage, MaxStackSize, PotionContents, StoredEnchantments, Tool, - Unbreakable, Weapon, + Unbreakable, UseCooldown, Weapon, }; use crate::effect::{self, StatusEffect}; use crate::entity_type::EntityType; @@ -68,6 +68,7 @@ pub fn read_data(id: DataComponent, data: &NbtTag) -> Option Some(ConsumableImpl::read_data(data)?.to_dyn()), Equippable => Some(EquippableImpl::read_data(data)?.to_dyn()), StoredEnchantments => Some(StoredEnchantmentsImpl::read_data(data)?.to_dyn()), + UseCooldown => Some(UseCooldownImpl::read_data(data)?.to_dyn()), _ => None, } } @@ -720,8 +721,61 @@ impl Hash for ConsumableImpl { } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct UseRemainderImpl; -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct UseCooldownImpl; +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct UseCooldownImpl { + pub seconds: f32, + pub cooldown_group: Option, +} + +impl UseCooldownImpl { + pub fn new(seconds: f32, cooldown_group: Option) -> Self { + Self { + seconds, + cooldown_group, + } + } + + pub fn read_data(data: &NbtTag) -> Option { + let compound = data.extract_compound()?; + let seconds = compound.get_float("seconds")?; + let cooldown_group = compound.get_string("cooldown_group").map(|s| s.to_string()); + Some(Self { + seconds, + cooldown_group, + }) + } +} + +impl DataComponentImpl for UseCooldownImpl { + fn write_data(&self) -> NbtTag { + let mut compound = NbtCompound::new(); + compound.put_float("seconds", self.seconds); + if let Some(group) = &self.cooldown_group { + compound.put_string("cooldown_group", group.clone()); + } + NbtTag::Compound(compound) + } + + fn get_hash(&self) -> i32 { + let mut digest = Digest::new(Crc32Iscsi); + digest.update(&get_f32_hash(self.seconds).to_le_bytes()); + if let Some(group) = &self.cooldown_group { + digest.update(&get_str_hash(group).to_le_bytes()); + } + digest.finalize() as i32 + } + + default_impl!(UseCooldown); +} + +impl Eq for UseCooldownImpl {} + +impl Hash for UseCooldownImpl { + fn hash(&self, state: &mut H) { + self.seconds.to_bits().hash(state); + self.cooldown_group.hash(state); + } +} #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub enum DamageResistantType { /// Damage always dealt to ender dragons diff --git a/pumpkin-data/src/item_stack/mod.rs b/pumpkin-data/src/item_stack/mod.rs index 28386f2dd..9282315b2 100644 --- a/pumpkin-data/src/item_stack/mod.rs +++ b/pumpkin-data/src/item_stack/mod.rs @@ -2,8 +2,8 @@ use crate::data_component::DataComponent; use crate::data_component::DataComponent::Enchantments; use crate::data_component_impl::{ BlocksAttacksImpl, ConsumableImpl, DamageImpl, DataComponentImpl, EnchantmentsImpl, IDSet, - MaxDamageImpl, MaxStackSizeImpl, ToolImpl, UnbreakableImpl, get, get_mut, read_data, - read_data_pnbt, + MaxDamageImpl, MaxStackSizeImpl, ToolImpl, UnbreakableImpl, UseCooldownImpl, get, get_mut, + read_data, read_data_pnbt, }; use crate::item::Item; use crate::recipes::RecipeResultStruct; @@ -131,6 +131,11 @@ impl ItemStack { .map(|value| value.max_damage) } + #[must_use] + pub fn get_use_cooldown(&self) -> Option<&UseCooldownImpl> { + self.get_data_component::() + } + #[must_use] pub fn get_damage(&self) -> i32 { self.get_data_component::() diff --git a/pumpkin-protocol/src/bedrock/server/mod.rs b/pumpkin-protocol/src/bedrock/server/mod.rs index 9909732a4..473f0b751 100644 --- a/pumpkin-protocol/src/bedrock/server/mod.rs +++ b/pumpkin-protocol/src/bedrock/server/mod.rs @@ -10,4 +10,5 @@ pub mod raknet; pub mod request_chunk_radius; pub mod request_network_settings; pub mod resource_pack_response; +pub mod set_local_player_as_initialized; pub mod text; diff --git a/pumpkin-protocol/src/bedrock/server/set_local_player_as_initialized.rs b/pumpkin-protocol/src/bedrock/server/set_local_player_as_initialized.rs new file mode 100644 index 000000000..cac359c6b --- /dev/null +++ b/pumpkin-protocol/src/bedrock/server/set_local_player_as_initialized.rs @@ -0,0 +1,16 @@ +use crate::{codec::var_ulong::VarULong, serial::PacketRead}; +use pumpkin_macros::packet; +use std::io::{Error, Read}; + +#[packet(113)] +pub struct SSetLocalPlayerAsInitialized { + pub runtime_entity_id: VarULong, +} + +impl PacketRead for SSetLocalPlayerAsInitialized { + fn read(reader: &mut R) -> Result { + Ok(Self { + runtime_entity_id: VarULong::read(reader)?, + }) + } +} diff --git a/pumpkin-protocol/src/codec/data_component.rs b/pumpkin-protocol/src/codec/data_component.rs index 08e78af00..9dc5b5b9e 100644 --- a/pumpkin-protocol/src/codec/data_component.rs +++ b/pumpkin-protocol/src/codec/data_component.rs @@ -7,7 +7,8 @@ use pumpkin_data::data_component_impl::{ ConsumableImpl, ConsumeAnimation, ConsumeEffect, CustomNameImpl, DamageImpl, DataComponentImpl, EnchantmentsImpl, EquipmentSlot, EquippableImpl, FireworkExplosionImpl, FireworkExplosionShape, FireworksImpl, IDSet, IDSetContent, IdOr, ItemModelImpl, MaxStackSizeImpl, PotionContentsImpl, - SoundEvent, StatusEffectInstance, StoredEnchantmentsImpl, UnbreakableImpl, get, + SoundEvent, StatusEffectInstance, StoredEnchantmentsImpl, UnbreakableImpl, UseCooldownImpl, + get, }; use pumpkin_data::effect::StatusEffect; use pumpkin_data::entity::EntityType; @@ -837,6 +838,7 @@ pub fn deserialize<'a, A: SeqAccess<'a>>( DataComponent::Consumable => Ok(ConsumableImpl::deserialize(seq)?.to_dyn()), DataComponent::Equippable => Ok(EquippableImpl::deserialize(seq)?.to_dyn()), DataComponent::StoredEnchantments => Ok(StoredEnchantmentsImpl::deserialize(seq)?.to_dyn()), + DataComponent::UseCooldown => Ok(UseCooldownImpl::deserialize(seq)?.to_dyn()), _ => Err(serde::de::Error::custom(format!("{id:?} (TODO)"))), } } @@ -858,6 +860,29 @@ pub fn serialize( DataComponent::Consumable => get::(value).serialize(seq), DataComponent::Equippable => get::(value).serialize(seq), DataComponent::StoredEnchantments => get::(value).serialize(seq), + DataComponent::UseCooldown => get::(value).serialize(seq), _ => todo!("{} not yet implemented", id.to_name()), } } + +impl DataComponentCodec for UseCooldownImpl { + fn serialize(&self, seq: &mut T) -> Result<(), T::Error> { + seq.serialize_field::("", &self.seconds)?; + seq.serialize_field::>("", &self.cooldown_group) + } + + fn deserialize<'a, A: SeqAccess<'a>>(seq: &mut A) -> Result { + let seconds = seq + .next_element::()? + .ok_or(de::Error::custom("No UseCooldownImpl seconds float!"))?; + let cooldown_group = seq + .next_element::>()? + .ok_or(de::Error::custom( + "No UseCooldownImpl cooldown_group optional string!", + ))?; + Ok(Self { + seconds, + cooldown_group, + }) + } +} diff --git a/pumpkin-protocol/src/java/client/play/item_cooldown.rs b/pumpkin-protocol/src/java/client/play/item_cooldown.rs new file mode 100644 index 000000000..ef6aebd7a --- /dev/null +++ b/pumpkin-protocol/src/java/client/play/item_cooldown.rs @@ -0,0 +1,19 @@ +use pumpkin_data::packet::clientbound::PLAY_COOLDOWN; + +use crate::codec::var_int::VarInt; +use pumpkin_macros::java_packet; +use serde::Serialize; + +#[derive(Serialize)] +#[java_packet(PLAY_COOLDOWN)] +pub struct CItemCooldown { + pub group: String, + pub cooldown: VarInt, +} + +impl CItemCooldown { + #[must_use] + pub const fn new(group: String, cooldown: VarInt) -> Self { + Self { group, cooldown } + } +} diff --git a/pumpkin-protocol/src/java/client/play/mod.rs b/pumpkin-protocol/src/java/client/play/mod.rs index feff59037..84865d700 100644 --- a/pumpkin-protocol/src/java/client/play/mod.rs +++ b/pumpkin-protocol/src/java/client/play/mod.rs @@ -33,6 +33,7 @@ mod game_event; mod head_rot; mod hurt_animation; mod initialize_world_border; +mod item_cooldown; mod keep_alive; mod level_event; mod light_update; @@ -133,6 +134,7 @@ pub use game_event::*; pub use head_rot::*; pub use hurt_animation::*; pub use initialize_world_border::*; +pub use item_cooldown::*; pub use keep_alive::*; pub use level_event::*; pub use light_update::*; diff --git a/pumpkin-world/src/lighting/runtime.rs b/pumpkin-world/src/lighting/runtime.rs index 1b01a6f49..d25206a90 100644 --- a/pumpkin-world/src/lighting/runtime.rs +++ b/pumpkin-world/src/lighting/runtime.rs @@ -183,13 +183,13 @@ impl DynamicLightEngine { // No self-emission, darken it completely and continue propagation self.set_block_light_level(level, &neighbor_pos, 0) .await - .unwrap(); + .ok(); self.queue_block_light_decrease(neighbor_pos, neighbor_light); } else { // Has self-emission, set to its own light and re-propagate from it self.set_block_light_level(level, &neighbor_pos, neighbor_luminance) .await - .unwrap(); + .ok(); self.queue_block_light_increase(neighbor_pos, neighbor_luminance); } } else { @@ -204,11 +204,11 @@ impl DynamicLightEngine { pub async fn check_block_light_updates(&self, level: &Arc, pos: BlockPos) { match level.lighting_config { LightingEngineConfig::Full => { - self.set_block_light_level(level, &pos, 15).await.unwrap(); + self.set_block_light_level(level, &pos, 15).await.ok(); return; } LightingEngineConfig::Dark => { - self.set_block_light_level(level, &pos, 0).await.unwrap(); + self.set_block_light_level(level, &pos, 0).await.ok(); return; } LightingEngineConfig::Default => {} @@ -223,13 +223,13 @@ impl DynamicLightEngine { // Set to expected value immediately, then queue decrease to darken neighbors self.set_block_light_level(level, &pos, expected_light) .await - .unwrap(); + .ok(); self.queue_block_light_decrease(pos, current_light); } else if expected_light > current_light { // Handle light increase (placing light source) self.set_block_light_level(level, &pos, expected_light) .await - .unwrap(); + .ok(); self.queue_block_light_increase(pos, expected_light); } @@ -318,7 +318,7 @@ impl DynamicLightEngine { if new_light > neighbor_light { self.set_sky_light_level(level, &neighbor_pos, new_light) .await - .unwrap(); + .ok(); if new_light > 0 { self.queue_sky_light_increase(neighbor_pos, new_light); @@ -353,9 +353,7 @@ impl DynamicLightEngine { if neighbor_light == expected || neighbor_light < removed_light { // This neighbor was lit by us, darken it - self.set_sky_light_level(level, &neighbor_pos, 0) - .await - .unwrap(); + self.set_sky_light_level(level, &neighbor_pos, 0).await.ok(); self.queue_sky_light_decrease(neighbor_pos, neighbor_light); } else if neighbor_light > removed_light { // Neighbor has brighter light from another source @@ -368,7 +366,7 @@ impl DynamicLightEngine { pub async fn check_sky_light_updates(&self, level: &Arc, pos: BlockPos) { match level.lighting_config { LightingEngineConfig::Full => { - self.set_sky_light_level(level, &pos, 15).await.unwrap(); + self.set_sky_light_level(level, &pos, 15).await.ok(); return; } LightingEngineConfig::Dark => { @@ -423,13 +421,13 @@ impl DynamicLightEngine { // Light decreased self.set_sky_light_level(level, &pos, expected_light) .await - .unwrap(); + .ok(); self.queue_sky_light_decrease(pos, current_light); } else if expected_light > current_light { // Light increased self.set_sky_light_level(level, &pos, expected_light) .await - .unwrap(); + .ok(); self.queue_sky_light_increase(pos, expected_light); } diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index 2bb190bad..a4280315d 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -2203,6 +2203,16 @@ impl EntityBase for LivingEntity { item_lock.decrement_unless_creative(player.gamemode.load(), 1); } } + + if let Some(cooldown) = item.get_use_cooldown() { + let group = cooldown + .cooldown_group + .clone() + .unwrap_or_else(|| item.item.registry_key.to_string()); + player + .start_cooldown(group, (cooldown.seconds * 20.0) as i32) + .await; + } } self.clear_active_hand().await; diff --git a/pumpkin/src/entity/mob/bat.rs b/pumpkin/src/entity/mob/bat.rs index 9e54d79dc..8e8cda8b5 100644 --- a/pumpkin/src/entity/mob/bat.rs +++ b/pumpkin/src/entity/mob/bat.rs @@ -69,7 +69,7 @@ use pumpkin_nbt::pnbt::PNbtCompound; impl NBTStorage for BatEntity { fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { - self.mob_entity.living_entity.entity.write_nbt(nbt).await; + self.mob_entity.living_entity.write_nbt(nbt).await; let flags: u8 = if self.is_roosting() { ROOSTING_FLAG } else { 0 }; nbt.put_byte(flags as i8); }) @@ -77,11 +77,7 @@ impl NBTStorage for BatEntity { fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { - self.mob_entity - .living_entity - .entity - .read_nbt_non_mut(nbt) - .await; + self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; let flags = nbt.get_byte().unwrap_or(0) as u8; let roosting = (flags & ROOSTING_FLAG) != 0; self.set_roosting(roosting).await; diff --git a/pumpkin/src/entity/mob/creeper.rs b/pumpkin/src/entity/mob/creeper.rs index 115f2d8f9..d69487b6d 100644 --- a/pumpkin/src/entity/mob/creeper.rs +++ b/pumpkin/src/entity/mob/creeper.rs @@ -121,7 +121,7 @@ use pumpkin_nbt::pnbt::PNbtCompound; impl NBTStorage for CreeperEntity { fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - self.mob_entity.living_entity.entity.write_nbt(nbt).await; + self.mob_entity.living_entity.write_nbt(nbt).await; nbt.put_bool(self.charged.load(Ordering::Relaxed)); nbt.put_short(self.fuse_time.load(Ordering::Relaxed) as i16); nbt.put_byte(self.explosion_radius.load(Ordering::Relaxed) as i8); @@ -131,11 +131,7 @@ impl NBTStorage for CreeperEntity { fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - self.mob_entity - .living_entity - .entity - .read_nbt_non_mut(nbt) - .await; + self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; self.charged .store(nbt.get_bool().unwrap_or(false), Ordering::Relaxed); self.fuse_time diff --git a/pumpkin/src/entity/mob/shulker.rs b/pumpkin/src/entity/mob/shulker.rs index 8d0e88bf8..2eddff1f7 100644 --- a/pumpkin/src/entity/mob/shulker.rs +++ b/pumpkin/src/entity/mob/shulker.rs @@ -322,7 +322,7 @@ impl ShulkerEntity { impl NBTStorage for ShulkerEntity { fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { - self.mob_entity.living_entity.entity.write_nbt(nbt).await; + self.mob_entity.living_entity.write_nbt(nbt).await; nbt.put_byte(self.attach_face.load(Ordering::Relaxed) as i8); nbt.put_byte(self.peek_amount.load(Ordering::Relaxed) as i8); nbt.put_byte(self.color.load(Ordering::Relaxed) as i8); @@ -331,11 +331,7 @@ impl NBTStorage for ShulkerEntity { fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { - self.mob_entity - .living_entity - .entity - .read_nbt_non_mut(nbt) - .await; + self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; if let Ok(face) = nbt.get_byte() { self.attach_face.store(face as u8, Ordering::Relaxed); } diff --git a/pumpkin/src/entity/mob/slime.rs b/pumpkin/src/entity/mob/slime.rs index b630b1f3f..bfac3ae38 100644 --- a/pumpkin/src/entity/mob/slime.rs +++ b/pumpkin/src/entity/mob/slime.rs @@ -1,4 +1,3 @@ -use std::sync::atomic::Ordering::Relaxed; use std::sync::{Arc, Weak}; use pumpkin_data::entity::EntityType; @@ -64,19 +63,13 @@ use pumpkin_nbt::pnbt::PNbtCompound; impl NBTStorage for SlimeEntity { fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { - self.entity.living_entity.entity.write_nbt(nbt).await; - nbt.put_int(self.entity.living_entity.entity.data.load(Relaxed)); + self.entity.living_entity.write_nbt(nbt).await; }) } fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { - self.entity.living_entity.entity.read_nbt_non_mut(nbt).await; - self.entity - .living_entity - .entity - .data - .store(nbt.get_int().unwrap_or(0), Relaxed); + self.entity.living_entity.read_nbt_non_mut(nbt).await; }) } } diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index e1b49ee3e..bb372e67e 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -2694,7 +2694,7 @@ impl NBTStorage for Entity { fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async move { let position = self.pos.load(); - nbt.put_string(&format!("minecraft:{}", self.entity_type.resource_name)); + nbt.put_string(self.entity_type.resource_name); nbt.put_uuid(&self.entity_uuid); // Pos diff --git a/pumpkin/src/entity/passive/sheep.rs b/pumpkin/src/entity/passive/sheep.rs index 07b92cae6..0ddd0cf08 100644 --- a/pumpkin/src/entity/passive/sheep.rs +++ b/pumpkin/src/entity/passive/sheep.rs @@ -110,7 +110,7 @@ use pumpkin_nbt::pnbt::PNbtCompound; impl NBTStorage for SheepEntity { fn write_nbt<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - self.mob_entity.living_entity.entity.write_nbt(nbt).await; + self.mob_entity.living_entity.write_nbt(nbt).await; nbt.put_bool(self.is_sheared()); nbt.put_byte(self.get_color() as i8); }) @@ -118,11 +118,7 @@ impl NBTStorage for SheepEntity { fn read_nbt_non_mut<'a>(&'a self, nbt: &'a mut PNbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { - self.mob_entity - .living_entity - .entity - .read_nbt_non_mut(nbt) - .await; + self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; let sheared = nbt.get_bool().unwrap_or(false); let color = nbt.get_byte().unwrap_or(0) as u8; let byte = (color & 0x0F) | if sheared { 0x10 } else { 0 }; diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index e8dbca80d..089c82fb4 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -15,10 +15,12 @@ use pumpkin_data::meta_data_type::MetaDataType; use pumpkin_data::tracked_data::TrackedData; use pumpkin_inventory::player::ender_chest_inventory::EnderChestInventory; use pumpkin_protocol::bedrock::client::level_chunk::CLevelChunk; +use pumpkin_protocol::bedrock::client::play_status::CPlayStatus; use pumpkin_protocol::bedrock::client::set_time::CSetTime; use pumpkin_protocol::bedrock::client::update_abilities::{ Ability, AbilityLayer, CUpdateAbilities, }; +use pumpkin_protocol::bedrock::frame_set::FrameSet; use pumpkin_protocol::bedrock::server::text::SText; use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_world::chunk::{ChunkData, ChunkEntityData}; @@ -56,8 +58,8 @@ use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::java::client::play::{ Animation, CAcknowledgeBlockChange, CActionBar, CChangeDifficulty, CChunkBatchEnd, CChunkBatchStart, CChunkData, CCloseContainer, CCombatDeath, CCustomPayload, - CDisguisedChatMessage, CEntityAnimation, CEntityPositionSync, CGameEvent, CKeepAlive, - COpenScreen, CParticle, CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, + CDisguisedChatMessage, CEntityAnimation, CEntityPositionSync, CGameEvent, CItemCooldown, + CKeepAlive, COpenScreen, CParticle, CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, CPlayerSpawnPosition, CRespawn, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem, CSetEquipment, CSetExperience, CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle, CSystemChatMessage, CTabList, @@ -365,6 +367,12 @@ impl ChunkManager { /// Represents a Minecraft player entity. /// /// A `Player` is a special type of entity that represents a human player connected to the server. +#[derive(Clone, Copy, Debug)] +pub struct ItemCooldown { + pub start_tick: i32, + pub duration: i32, +} + pub struct Player { /// The underlying living entity object that represents the player. pub living_entity: LivingEntity, @@ -437,6 +445,7 @@ pub struct Player { pub permission_lvl: AtomicCell, /// Whether the client has reported that it has loaded. pub client_loaded: AtomicBool, + pub bedrock_spawned: AtomicBool, /// The amount of time (in ticks) the client has to report having finished loading before being timed out. pub client_loaded_timeout: AtomicU32, /// Item usage tracking for bows, crossbows, etc. @@ -449,6 +458,7 @@ pub struct Player { pub experience_progress: AtomicCell, /// The player's total experience points. pub experience_points: AtomicI32, + pub item_cooldowns: Mutex>, pub experience_pick_up_delay: Mutex, pub chunk_manager: Mutex, pub has_played_before: AtomicBool, @@ -551,6 +561,7 @@ impl Player { ping: AtomicU32::new(0), last_attacked_ticks: AtomicU32::new(0), client_loaded: AtomicBool::new(false), + bedrock_spawned: AtomicBool::new(false), client_loaded_timeout: AtomicU32::new(60), // Item usage tracking using_item: AtomicBool::new(false), @@ -573,6 +584,7 @@ impl Player { experience_level: AtomicI32::new(0), experience_progress: AtomicCell::new(0.0), experience_points: AtomicI32::new(0), + item_cooldowns: Mutex::new(HashMap::new()), // Default to sending 16 chunks per tick. chunk_manager: Mutex::new(ChunkManager::new( 16, @@ -608,6 +620,44 @@ impl Player { .await; } + pub async fn start_cooldown(&self, group: String, duration: i32) { + let mut cooldowns = self.item_cooldowns.lock().await; + cooldowns.insert( + group.clone(), + ItemCooldown { + start_tick: self.tick_counter.load(Ordering::Relaxed), + duration, + }, + ); + self.client + .send_packet_now(&CItemCooldown::new(group, VarInt(duration))) + .await; + } + + pub async fn get_cooldown(&self, group: &str) -> f32 { + let cooldowns = self.item_cooldowns.lock().await; + if let Some(cooldown) = cooldowns.get(group) { + let current_tick = self.tick_counter.load(Ordering::Relaxed); + let elapsed = current_tick - cooldown.start_tick; + if elapsed < cooldown.duration { + return 1.0 - (elapsed as f32 / cooldown.duration as f32); + } + } + 0.0 + } + + pub async fn is_on_cooldown(&self, group: &str) -> bool { + let mut cooldowns = self.item_cooldowns.lock().await; + if let Some(cooldown) = cooldowns.get(group) { + let current_tick = self.tick_counter.load(Ordering::Relaxed); + if current_tick - cooldown.start_tick < cooldown.duration { + return true; + } + cooldowns.remove(group); + } + false + } + pub async fn set_display_name(&self, display_name: Option) { *self.display_name.lock().await = display_name.clone(); // Update the tab list for everyone @@ -1612,6 +1662,16 @@ impl Player { }) .await; } + + if !self.bedrock_spawned.load(Ordering::Relaxed) && chunk_count > 4 { + let mut frame_set = FrameSet::default(); + + bedrock_client + .write_game_packet_to_set(&CPlayStatus::PlayerSpawn, &mut frame_set) + .await; + bedrock_client.send_frame_set(frame_set, 0x84).await; + self.bedrock_spawned.store(true, Ordering::Relaxed); + } } } } diff --git a/pumpkin/src/item/registry.rs b/pumpkin/src/item/registry.rs index e1d77ef00..fb4d07522 100644 --- a/pumpkin/src/item/registry.rs +++ b/pumpkin/src/item/registry.rs @@ -26,11 +26,27 @@ impl ItemRegistry { } } - pub async fn on_use(&self, item: &Item, player: &Player) { + pub async fn on_use(&self, stack: &ItemStack, player: &Player) { + let item = stack.item; + let cooldown = stack.get_use_cooldown(); + let cooldown_group = cooldown + .and_then(|c| c.cooldown_group.clone()) + .unwrap_or_else(|| item.registry_key.to_string()); + + if player.is_on_cooldown(&cooldown_group).await { + return; + } + let pumpkin_item = self.get_pumpkin_item(item.id); if let Some(pumpkin_item) = pumpkin_item { pumpkin_item.normal_use(item, player).await; } + + if let Some(cooldown) = cooldown { + player + .start_cooldown(cooldown_group, (cooldown.seconds * 20.0) as i32) + .await; + } } pub async fn on_stopped_using(&self, stack: &ItemStack, player: &Player) { @@ -59,12 +75,28 @@ impl ItemRegistry { block: &Block, server: &Server, ) { + let cooldown = stack.get_use_cooldown().cloned(); + let cooldown_group = cooldown + .as_ref() + .and_then(|c| c.cooldown_group.clone()) + .unwrap_or_else(|| stack.item.registry_key.to_string()); + + if player.is_on_cooldown(&cooldown_group).await { + return; + } + let pumpkin_item = self.get_pumpkin_item(stack.item.id); if let Some(pumpkin_item) = pumpkin_item { pumpkin_item .use_on_block(stack, player, location, face, cursor_pos, block, server) .await; } + + if let Some(cooldown) = cooldown { + player + .start_cooldown(cooldown_group, (cooldown.seconds * 20.0) as i32) + .await; + } } pub async fn use_on_entity( @@ -73,10 +105,26 @@ impl ItemRegistry { player: &Player, entity: Arc, ) { + let cooldown = stack.get_use_cooldown().cloned(); + let cooldown_group = cooldown + .as_ref() + .and_then(|c| c.cooldown_group.clone()) + .unwrap_or_else(|| stack.item.registry_key.to_string()); + + if player.is_on_cooldown(&cooldown_group).await { + return; + } + let pumpkin_item = self.get_pumpkin_item(stack.item.id); if let Some(pumpkin_item) = pumpkin_item { pumpkin_item.use_on_entity(stack, player, entity).await; } + + if let Some(cooldown) = cooldown { + player + .start_cooldown(cooldown_group, (cooldown.seconds * 20.0) as i32) + .await; + } } pub fn can_mine(&self, item: &Item, player: &Player) -> bool { diff --git a/pumpkin/src/net/bedrock/mod.rs b/pumpkin/src/net/bedrock/mod.rs index 77614254e..cc24826b0 100644 --- a/pumpkin/src/net/bedrock/mod.rs +++ b/pumpkin/src/net/bedrock/mod.rs @@ -26,7 +26,6 @@ use pumpkin_protocol::{ command_request::SCommandRequest, container_close::SContainerClose, interaction::SInteraction, - loading_screen::SLoadingScreen, login::SLogin, player_action::SPlayerAction, player_auth_input::SPlayerAuthInput, @@ -40,6 +39,7 @@ use pumpkin_protocol::{ request_chunk_radius::SRequestChunkRadius, request_network_settings::SRequestNetworkSettings, resource_pack_response::SResourcePackResponse, + set_local_player_as_initialized::SSetLocalPlayerAsInitialized, text::SText, }, }, @@ -294,7 +294,7 @@ impl BedrockClient { let mut packet_buf = Vec::new(); match self.write_game_packet(packet, &mut packet_buf).await { Ok(()) => { - self.send_framed_packet_data(packet_buf, RakReliability::Unreliable) + self.send_framed_packet_data(packet_buf, RakReliability::ReliableOrdered) .await; } Err(err) => error!("Failed to write game packet: {err}"), @@ -688,11 +688,6 @@ impl BedrockClient { self.player_pos_update(player, SPlayerAuthInput::read(reader)?, server) .await; } - SLoadingScreen::PACKET_ID => { - if SLoadingScreen::read(reader)?.is_loading_done() { - player.set_client_loaded(true); - } - } SRequestChunkRadius::PACKET_ID => { self.handle_request_chunk_radius(player, SRequestChunkRadius::read(reader)?) .await; @@ -713,6 +708,12 @@ impl BedrockClient { self.handle_chat_command(player, server, SCommandRequest::read(reader)?) .await; } + SSetLocalPlayerAsInitialized::PACKET_ID => { + self.handle_set_local_player_as_initialized( + player, + &SSetLocalPlayerAsInitialized::read(reader)?, + ); + } SPlayerAction::PACKET_ID => { self.handle_player_action(player, server, SPlayerAction::read(reader)?) .await; diff --git a/pumpkin/src/net/bedrock/play.rs b/pumpkin/src/net/bedrock/play.rs index 490665019..6a693740c 100644 --- a/pumpkin/src/net/bedrock/play.rs +++ b/pumpkin/src/net/bedrock/play.rs @@ -14,6 +14,7 @@ use pumpkin_protocol::{ player_action::{Action as PlayerAction, SPlayerAction}, player_auth_input::{InputData, SPlayerAuthInput}, request_chunk_radius::SRequestChunkRadius, + set_local_player_as_initialized::SSetLocalPlayerAsInitialized, text::SText, }, }, @@ -73,13 +74,24 @@ impl BedrockClient { old_vd }; - if old_view_distance.get() != view_distance as u8 { - debug!( - "Player {} updated their render distance: {} -> {}.", - player.gameprofile.name, old_view_distance, view_distance - ); - chunker::update_position(player).await; - } + debug!( + "Player {} updated their render distance: {} -> {}.", + player.gameprofile.name, old_view_distance, view_distance + ); + chunker::update_position(player).await; + } + + pub fn handle_set_local_player_as_initialized( + &self, + player: &Arc, + packet: &SSetLocalPlayerAsInitialized, + ) { + debug!( + "Player {} initialized (Runtime ID: {})", + player.gameprofile.name, packet.runtime_entity_id.0 + ); + // This is sent when the client has finished loading and rendering the world. + player.set_client_loaded(true); } pub async fn player_pos_update( diff --git a/pumpkin/src/net/java/play.rs b/pumpkin/src/net/java/play.rs index d7606d754..d723bdd61 100644 --- a/pumpkin/src/net/java/play.rs +++ b/pumpkin/src/net/java/play.rs @@ -2278,9 +2278,9 @@ impl JavaClient { self.prepare_hand_item_for_use(player, hand, &item_in_hand) .await; - let item_for_use = { + let (item_for_use, stack_for_use) = { let held = item_in_hand.lock().await; - held.item + (held.item, held.clone()) }; if !self @@ -2294,7 +2294,7 @@ impl JavaClient { server; event; 'after: { - server.item_registry.on_use(item_for_use, player).await; + server.item_registry.on_use(&stack_for_use, player).await; } }} } @@ -2307,6 +2307,17 @@ impl JavaClient { ) { let inventory = player.inventory(); let mut held = item_in_hand.lock().await; + + if let Some(cooldown) = held.get_use_cooldown() { + let group = cooldown + .cooldown_group + .clone() + .unwrap_or_else(|| held.item.registry_key.to_string()); + if player.is_on_cooldown(&group).await { + return; + } + } + if held.get_data_component::().is_some() { // If its food we want to make sure we can actually consume it if let Some(food) = held.get_data_component::() { diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index cc2a2c022..733b87b51 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -78,7 +78,6 @@ use pumpkin_protocol::{ client::{ creative_content::{CreativeContent, Group}, gamerules_changed::GameRules, - play_status::CPlayStatus, start_game::{Experiments, GamePublishSetting, LevelSettings}, update_artributes::{Attribute, CUpdateAttributes}, }, @@ -1806,9 +1805,6 @@ impl World { &mut frame_set, ) .await; - client - .write_game_packet_to_set(&CPlayStatus::PlayerSpawn, &mut frame_set) - .await; client.send_frame_set(frame_set, 0x84).await; }