diff --git a/pumpkin-util/src/math/vector3.rs b/pumpkin-util/src/math/vector3.rs index 54ba1e801..561a94768 100644 --- a/pumpkin-util/src/math/vector3.rs +++ b/pumpkin-util/src/math/vector3.rs @@ -1,7 +1,7 @@ use bytes::BufMut; use std::ops::{Add, AddAssign, Div, Mul, Sub}; -use num_traits::Float; +use num_traits::{Float, Num}; #[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Default)] pub struct Vector3 { @@ -10,7 +10,7 @@ pub struct Vector3 { pub z: T, } -impl Vector3 { +impl Vector3 { pub const fn new(x: T, y: T, z: T) -> Self { Vector3 { x, y, z } } @@ -55,6 +55,43 @@ impl Vector3 { } } + pub fn lerp(&self, other: &Vector3, t: T) -> Self { + Vector3 { + x: self.x + (other.x - self.x) * t, + y: self.y + (other.y - self.y) * t, + z: self.z + (other.z - self.z) * t, + } + } + + pub fn sign(&self) -> Vector3 + where + T: Num + PartialOrd + Copy, + { + Vector3 { + x: if self.x > T::zero() { + 1 + } else if self.x < T::zero() { + -1 + } else { + 0 + }, + y: if self.y > T::zero() { + 1 + } else if self.y < T::zero() { + -1 + } else { + 0 + }, + z: if self.z > T::zero() { + 1 + } else if self.z < T::zero() { + -1 + } else { + 0 + }, + } + } + pub fn squared_distance_to_vec(&self, other: Self) -> T { self.squared_distance_to(other.x, other.y, other.z) } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index cec3f5349..811ed85a1 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -40,7 +40,7 @@ use pumpkin_config::{BASIC_CONFIG, advanced_config}; use pumpkin_data::{ BlockState, damage::DamageType, - entity::{EffectType, EntityStatus, EntityType}, + entity::{EffectType, EntityPose, EntityStatus, EntityType}, item::Operation, particle::Particle, sound::{Sound, SoundCategory}, @@ -716,6 +716,26 @@ impl Player { self.living_entity.entity.pos.load() } + pub fn eye_position(&self) -> Vector3 { + let eye_height = if self.living_entity.entity.pose.load() == EntityPose::Crouching { + 1.27 + } else { + f64::from(self.living_entity.entity.standing_eye_height) + }; + Vector3::new( + self.living_entity.entity.pos.load().x, + self.living_entity.entity.pos.load().y + eye_height, + self.living_entity.entity.pos.load().z, + ) + } + + pub fn rotation(&self) -> (f32, f32) { + ( + self.living_entity.entity.yaw.load(), + self.living_entity.entity.pitch.load(), + ) + } + /// Updates the current abilities the player has. pub async fn send_abilities_update(&self) { let mut b = 0i8; diff --git a/pumpkin/src/item/items/bucket.rs b/pumpkin/src/item/items/bucket.rs new file mode 100644 index 000000000..5b388709a --- /dev/null +++ b/pumpkin/src/item/items/bucket.rs @@ -0,0 +1,183 @@ +use std::sync::Arc; + +use crate::entity::player::Player; +use async_trait::async_trait; +use pumpkin_data::Block; +use pumpkin_data::fluid::Fluid; +use pumpkin_data::item::Item; +use pumpkin_inventory::player::PlayerInventory; +use pumpkin_protocol::client::play::CSetContainerSlot; +use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; +use pumpkin_util::GameMode; +use pumpkin_util::math::position::BlockPos; +use pumpkin_util::math::vector3::Vector3; +use pumpkin_world::item::ItemStack; + +use crate::item::pumpkin_item::{ItemMetadata, PumpkinItem}; +use crate::world::{BlockFlags, World}; + +pub struct EmptyBucketItem; +pub struct FilledBucketItem; + +impl ItemMetadata for EmptyBucketItem { + fn ids() -> Box<[u16]> { + [Item::BUCKET.id].into() + } +} + +impl ItemMetadata for FilledBucketItem { + fn ids() -> Box<[u16]> { + [ + Item::WATER_BUCKET.id, + Item::LAVA_BUCKET.id, + // TODO drink milk + // Item::MILK_BUCKET.id, + // TODO implement these buckets, and getting the item from the world + // Item::POWDER_SNOW_BUCKET.id, + // Item::AXOLOTL_BUCKET.id, + // Item::COD_BUCKET.id, + // Item::SALMON_BUCKET.id, + // Item::TROPICAL_FISH_BUCKET.id, + // Item::PUFFERFISH_BUCKET.id, + // Item::TADPOLE_BUCKET.id, + ] + .into() + } +} + +fn get_start_and_end_pos(player: &Player) -> (Vector3, Vector3) { + let start_pos = player.eye_position(); + let (yaw, pitch) = player.rotation(); + let (yaw_rad, pitch_rad) = (f64::from(yaw.to_radians()), f64::from(pitch.to_radians())); + let block_interaction_range = 4.5; // This is not the same as the block_interaction_range in the + // player entity. + let direction = Vector3::new( + -yaw_rad.sin() * pitch_rad.cos() * block_interaction_range, + -pitch_rad.sin() * block_interaction_range, + pitch_rad.cos() * yaw_rad.cos() * block_interaction_range, + ); + + let end_pos = start_pos.add(&direction); + (start_pos, end_pos) +} + +#[async_trait] +impl PumpkinItem for EmptyBucketItem { + #[allow(clippy::too_many_lines)] + async fn normal_use(&self, _item: &Item, player: &Player) { + let world = player.world().await.clone(); + let (start_pos, end_pos) = get_start_and_end_pos(player); + + let checker = async |pos: &BlockPos, world_inner: &Arc| { + let Ok(state_id) = world_inner.get_block_state_id(pos).await else { + return false; + }; + + state_id == Block::WATER.default_state_id || state_id == Block::LAVA.default_state_id + }; + + let (block_pos, _) = world.raytrace(start_pos, end_pos, checker).await; + + if let Some(pos) = block_pos { + let Ok(state_id) = world.get_block_state_id(&pos).await else { + return; + }; + + world + .set_block_state(&pos, Block::AIR.id, BlockFlags::NOTIFY_NEIGHBORS) + .await; + let mut inventory = player.inventory().lock().await; + let selected = inventory.get_selected_slot(); + let item_type = if state_id == Block::WATER.default_state_id { + Item::WATER_BUCKET + } else { + Item::LAVA_BUCKET + }; + let item_stack = Some(ItemStack::new(1, item_type.clone())); + let slot_data = ItemStackSerializer::from(item_stack.clone()); + let game_mode = player.gamemode.load(); + if game_mode == GameMode::Creative { + let slot = inventory.get_pickup_item_slot(item_type.id); + if let Some(slot) = slot { + if let Err(err) = inventory.set_slot(slot, item_stack, false) { + log::error!("Failed to set slot: {err}"); + } else { + let dest_packet = CSetContainerSlot::new( + PlayerInventory::CONTAINER_ID, + inventory.state_id as i32, + slot as i16, + &slot_data, + ); + player.client.enqueue_packet(&dest_packet).await; + } + } + } else if let Err(err) = inventory.set_slot(selected, item_stack.clone(), false) { + log::error!("Failed to set slot: {err}"); + } else { + let dest_packet = CSetContainerSlot::new( + PlayerInventory::CONTAINER_ID, + inventory.state_id as i32, + selected as i16, + &slot_data, + ); + player.client.enqueue_packet(&dest_packet).await; + } + } + } +} + +#[async_trait] +impl PumpkinItem for FilledBucketItem { + async fn normal_use(&self, item: &Item, player: &Player) { + if item.id == Item::MILK_BUCKET.id { + // TODO implement milk bucket + return; + } + + let world = player.world().await.clone(); + let (start_pos, end_pos) = get_start_and_end_pos(player); + let checker = async |pos: &BlockPos, world_inner: &Arc| { + let Ok(state_id) = world_inner.get_block_state_id(pos).await else { + return false; + }; + if Fluid::from_state_id(state_id).is_some() { + return false; + } + state_id != Block::AIR.id + }; + + let (block_pos, block_direction) = world.raytrace(start_pos, end_pos, checker).await; + + if let (Some(pos), Some(direction)) = (block_pos, block_direction) { + world + .set_block_state( + &pos.offset(direction.to_offset()), + // Block::WATER.default_state_id, + if item.id == Item::WATER_BUCKET.id { + Block::WATER.default_state_id + } else { + Block::LAVA.default_state_id + }, + BlockFlags::NOTIFY_NEIGHBORS, + ) + .await; + if player.gamemode.load() != GameMode::Creative { + let mut inventory = player.inventory().lock().await; + let selected = inventory.get_selected_slot(); + let item = Some(ItemStack::new(1, Item::BUCKET)); + let slot_data = ItemStackSerializer::from(item.clone()); + if let Err(err) = inventory.set_slot(selected, item, false) { + log::error!("Failed to set slot: {err}"); + } else { + let dest_packet = CSetContainerSlot::new( + PlayerInventory::CONTAINER_ID, + inventory.state_id as i32, + selected as i16, + &slot_data, + ); + player.client.enqueue_packet(&dest_packet).await; + } + } + } + } +} diff --git a/pumpkin/src/item/items/mod.rs b/pumpkin/src/item/items/mod.rs index ba8c458cf..8ea9fea5a 100644 --- a/pumpkin/src/item/items/mod.rs +++ b/pumpkin/src/item/items/mod.rs @@ -1,4 +1,5 @@ mod axe; +mod bucket; mod egg; mod flint_and_steel; mod hoe; @@ -9,6 +10,7 @@ mod sword; mod trident; use axe::AxeItem; +use bucket::{EmptyBucketItem, FilledBucketItem}; use egg::EggItem; use flint_and_steel::FlintAndSteelItem; use hoe::HoeItem; @@ -30,6 +32,8 @@ pub fn default_registry() -> Arc { manager.register(FlintAndSteelItem); manager.register(SwordItem); manager.register(TridentItem); + manager.register(EmptyBucketItem); + manager.register(FilledBucketItem); manager.register(ShovelItem); manager.register(AxeItem); manager.register(HoneyCombItem); diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index ba5cbb5dd..cb7b5fef9 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -1418,7 +1418,7 @@ impl Player { .item_registry .use_on_block(&stack.item, self, location, face, &block, server) .await; - + self.update_sequence(use_item_on.sequence.0); let action_result = server .block_registry .use_with_item(&block, self, location, &stack.item, server, world) @@ -1484,13 +1484,19 @@ impl Player { world.add_block_entity(Arc::new(updated_sign)).await; } - pub async fn handle_use_item(&self, _use_item: &SUseItem, server: &Server) { + pub async fn handle_use_item(&self, use_item: &SUseItem, server: &Server) { if !self.has_client_loaded() { return; } - if let Some(held) = self.inventory().lock().await.held_item() { - server.item_registry.on_use(&held.item, self).await; + let held = { + let inventory = self.inventory().lock().await; + inventory.held_item().cloned() + }; + + if held.is_some() { + server.item_registry.on_use(&held.unwrap().item, self).await; } + self.update_sequence(use_item.sequence.0); } pub async fn handle_set_held_item(&self, held: SSetHeldItem) { diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index eee5e8a02..07481311e 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1729,4 +1729,106 @@ impl World { chunk.block_entities.remove(block_pos); chunk.dirty = true; } + + pub async fn raytrace( + self: &Arc, + start_pos: Vector3, + end_pos: Vector3, + hit_check: impl AsyncFn(&BlockPos, &Arc) -> bool, + ) -> (Option, Option) { + if start_pos == end_pos { + return (None, None); + } + + let adjust = -1.0e-7f64; + let to = end_pos.lerp(&start_pos, adjust); + let from = start_pos.lerp(&end_pos, adjust); + + let mut block = BlockPos::floored(from.x, from.y, from.z); + + if hit_check(&block, self).await { + return (Some(block), None); + } + + let difference = to.sub(&from); + + let step = difference.sign(); + + let delta = Vector3::new( + if step.x == 0 { + f64::MAX + } else { + (f64::from(step.x)) / difference.x + }, + if step.y == 0 { + f64::MAX + } else { + (f64::from(step.y)) / difference.y + }, + if step.z == 0 { + f64::MAX + } else { + (f64::from(step.z)) / difference.z + }, + ); + + let mut next = Vector3::new( + delta.x + * (if step.x > 0 { + 1.0 - (from.x - from.x.floor()) + } else { + from.x - from.x.floor() + }), + delta.y + * (if step.y > 0 { + 1.0 - (from.y - from.y.floor()) + } else { + from.y - from.y.floor() + }), + delta.z + * (if step.z > 0 { + 1.0 - (from.z - from.z.floor()) + } else { + from.z - from.z.floor() + }), + ); + + while next.x <= 1.0 || next.y <= 1.0 || next.z <= 1.0 { + let block_direction = match (next.x, next.y, next.z) { + (x, y, z) if x < y && x < z => { + block.0.x += step.x; + next.x += delta.x; + if step.x > 0 { + BlockDirection::West + } else { + BlockDirection::East + } + } + (_, y, z) if y < z => { + block.0.y += step.y; + next.y += delta.y; + if step.y > 0 { + BlockDirection::Down + } else { + BlockDirection::Up + } + } + _ => { + block.0.z += step.z; + next.z += delta.z; + if step.z > 0 { + BlockDirection::North + } else { + BlockDirection::South + } + } + }; + + if hit_check(&block, self).await { + return (Some(block), Some(block_direction)); + } + } + + (None, None) + } }