fix: ender pearl delay

This commit is contained in:
Alexander Medvedev 2026-05-02 14:06:49 +02:00
parent b0930dd6eb
commit d33484d057
21 changed files with 312 additions and 77 deletions

View File

@ -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<Box<dyn DataCompone
Consumable => 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<String>,
}
impl UseCooldownImpl {
pub fn new(seconds: f32, cooldown_group: Option<String>) -> Self {
Self {
seconds,
cooldown_group,
}
}
pub fn read_data(data: &NbtTag) -> Option<Self> {
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<H: std::hash::Hasher>(&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

View File

@ -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::<UseCooldownImpl>()
}
#[must_use]
pub fn get_damage(&self) -> i32 {
self.get_data_component::<DamageImpl>()

View File

@ -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;

View File

@ -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<R: Read>(reader: &mut R) -> Result<Self, Error> {
Ok(Self {
runtime_entity_id: VarULong::read(reader)?,
})
}
}

View File

@ -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<T: SerializeStruct>(
DataComponent::Consumable => get::<ConsumableImpl>(value).serialize(seq),
DataComponent::Equippable => get::<EquippableImpl>(value).serialize(seq),
DataComponent::StoredEnchantments => get::<StoredEnchantmentsImpl>(value).serialize(seq),
DataComponent::UseCooldown => get::<UseCooldownImpl>(value).serialize(seq),
_ => todo!("{} not yet implemented", id.to_name()),
}
}
impl DataComponentCodec<Self> for UseCooldownImpl {
fn serialize<T: SerializeStruct>(&self, seq: &mut T) -> Result<(), T::Error> {
seq.serialize_field::<f32>("", &self.seconds)?;
seq.serialize_field::<Option<String>>("", &self.cooldown_group)
}
fn deserialize<'a, A: SeqAccess<'a>>(seq: &mut A) -> Result<Self, A::Error> {
let seconds = seq
.next_element::<f32>()?
.ok_or(de::Error::custom("No UseCooldownImpl seconds float!"))?;
let cooldown_group = seq
.next_element::<Option<String>>()?
.ok_or(de::Error::custom(
"No UseCooldownImpl cooldown_group optional string!",
))?;
Ok(Self {
seconds,
cooldown_group,
})
}
}

View File

@ -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 }
}
}

View File

@ -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::*;

View File

@ -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<Level>, 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<Level>, 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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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);
}

View File

@ -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;
})
}
}

View File

@ -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

View File

@ -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 };

View File

@ -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<PermissionLvl>,
/// 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<f32>,
/// The player's total experience points.
pub experience_points: AtomicI32,
pub item_cooldowns: Mutex<HashMap<String, ItemCooldown>>,
pub experience_pick_up_delay: Mutex<u32>,
pub chunk_manager: Mutex<ChunkManager>,
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<TextComponent>) {
*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);
}
}
}
}

View File

@ -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<dyn EntityBase>,
) {
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 {

View File

@ -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;

View File

@ -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<Player>,
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(

View File

@ -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::<ConsumableImpl>().is_some() {
// If its food we want to make sure we can actually consume it
if let Some(food) = held.get_data_component::<FoodImpl>() {

View File

@ -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;
}