From f020ecf52ef54c651f63c4cde490564959d023ae Mon Sep 17 00:00:00 2001 From: OfficialKris <37947442+OfficialKris@users.noreply.github.com> Date: Sat, 4 Jan 2025 22:42:28 -0800 Subject: [PATCH 1/6] Initial splash potion implementation --- .../src/client/play/c_spawn_entity.rs | 6 +- pumpkin-protocol/src/codec/mod.rs | 1 + pumpkin-protocol/src/codec/slot.rs | 70 +++++++- .../src/codec/slot_components/mod.rs | 3 + .../codec/slot_components/potion_contents.rs | 150 ++++++++++++++++++ pumpkin-util/src/math/vector3.rs | 23 +++ pumpkin/src/entity/player.rs | 51 +++--- pumpkin/src/net/combat.rs | 9 +- pumpkin/src/net/packet/play.rs | 38 ++++- pumpkin/src/server/mod.rs | 61 +++++++ pumpkin/src/world/mod.rs | 31 +++- 11 files changed, 401 insertions(+), 42 deletions(-) create mode 100644 pumpkin-protocol/src/codec/slot_components/mod.rs create mode 100644 pumpkin-protocol/src/codec/slot_components/potion_contents.rs diff --git a/pumpkin-protocol/src/client/play/c_spawn_entity.rs b/pumpkin-protocol/src/client/play/c_spawn_entity.rs index 7888e8bfe..dcfe88760 100644 --- a/pumpkin-protocol/src/client/play/c_spawn_entity.rs +++ b/pumpkin-protocol/src/client/play/c_spawn_entity.rs @@ -10,7 +10,7 @@ pub struct CSpawnEntity { entity_id: VarInt, #[serde(with = "uuid::serde::compact")] entity_uuid: uuid::Uuid, - typ: VarInt, + entity_type: VarInt, x: f64, y: f64, z: f64, @@ -28,7 +28,7 @@ impl CSpawnEntity { pub fn new( entity_id: VarInt, entity_uuid: uuid::Uuid, - typ: VarInt, + entity_type: VarInt, x: f64, y: f64, z: f64, @@ -43,7 +43,7 @@ impl CSpawnEntity { Self { entity_id, entity_uuid, - typ, + entity_type, x, y, z, diff --git a/pumpkin-protocol/src/codec/mod.rs b/pumpkin-protocol/src/codec/mod.rs index 57af2e198..bfa47624b 100644 --- a/pumpkin-protocol/src/codec/mod.rs +++ b/pumpkin-protocol/src/codec/mod.rs @@ -6,6 +6,7 @@ use thiserror::Error; pub mod bit_set; pub mod identifier; pub mod slot; +pub mod slot_components; pub mod var_int; pub mod var_long; diff --git a/pumpkin-protocol/src/codec/slot.rs b/pumpkin-protocol/src/codec/slot.rs index f8dee1187..dd0c8df03 100644 --- a/pumpkin-protocol/src/codec/slot.rs +++ b/pumpkin-protocol/src/codec/slot.rs @@ -1,3 +1,4 @@ +use crate::codec::slot_components::PotionContents; use crate::VarInt; use pumpkin_world::item::ItemStack; use serde::ser::SerializeSeq; @@ -16,6 +17,36 @@ pub struct Slot { components_to_remove: Option>, } +macro_rules! back_to_enum { + ($(#[$meta:meta])* $vis:vis enum $name:ident { + $($(#[$vmeta:meta])* $vname:ident $(= $val:expr)?,)* + }) => { + $(#[$meta])* + $vis enum $name { + $($(#[$vmeta])* $vname $(= $val)?,)* + } + + impl std::convert::TryFrom for $name { + type Error = (); + + fn try_from(v: i32) -> Result { + match v { + $(x if x == $name::$vname as i32 => Ok($name::$vname),)* + _ => Err(()), + } + } + } + } +} +back_to_enum! { + pub enum StructuredComponentType { + CustomData, + // TODO: Implement all + PotionContents = 41, + + } +} + impl<'de> Deserialize<'de> for Slot { fn deserialize(deserializer: D) -> Result where @@ -55,12 +86,43 @@ impl<'de> Deserialize<'de> for Slot { let num_components_to_remove = seq .next_element::()? .ok_or(de::Error::custom("Failed to decode VarInt"))?; - if num_components_to_add.0 != 0 || num_components_to_remove.0 != 0 { - return Err(de::Error::custom( - "Slot components are currently unsupported", - )); + + for _ in 0..num_components_to_add.0 { + let component_type = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode VarInt!!"))?; + log::info!("dat: {:?}", component_type); + // let s: StructuredComponentType = component_type.into(); + match component_type.0.try_into() { + Ok(StructuredComponentType::PotionContents) => { + log::info!("yesir"); + let has_potion_id = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode potion"))?; + // let potion_id = seq + // .next_element::()? + // .ok_or(de::Error::custom("Failed to decode VarInt"))?; + } + Ok(StructuredComponentType::CustomData) => { + log::info!("uhhuh") + } + Err(_) => log::error!("nooooo"), + // _ => { + // log::error!("nooooo") + // } + } + // let component_data = seq + // .next_element::()? + // .ok_or(de::Error::custom("Unable to parse item"))?; + // array_of_changed_slots.push((slot_number, slot)); } + // if num_components_to_add.0 != 0 || num_components_to_remove.0 != 0 { + // return Err(de::Error::custom( + // "Slot components are currently unsupported", + // )); + // } + Ok(Slot { item_count, item_id: Some(item_id), diff --git a/pumpkin-protocol/src/codec/slot_components/mod.rs b/pumpkin-protocol/src/codec/slot_components/mod.rs new file mode 100644 index 000000000..9ef90d0b4 --- /dev/null +++ b/pumpkin-protocol/src/codec/slot_components/mod.rs @@ -0,0 +1,3 @@ +mod potion_contents; + +pub use potion_contents::*; diff --git a/pumpkin-protocol/src/codec/slot_components/potion_contents.rs b/pumpkin-protocol/src/codec/slot_components/potion_contents.rs new file mode 100644 index 000000000..7f3217cbd --- /dev/null +++ b/pumpkin-protocol/src/codec/slot_components/potion_contents.rs @@ -0,0 +1,150 @@ +use crate::codec::slot::StructuredComponentType; +use crate::VarInt; +use serde::{ + de::{self, SeqAccess}, + Deserialize, +}; + +#[derive(serde::Serialize, Debug, Clone)] +pub struct PotionContents { + has_potion_id: bool, + potion_id: Option, + has_custom_color: bool, + custom_color: Option, + number_of_custom_effects: VarInt, + custom_effects: Vec, + custom_name: String, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct PotionEffect { + type_id: VarInt, // todo: enum + detail: Detail, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct Detail { + amplifier: VarInt, + duration: VarInt, + ambient: bool, + show_particles: bool, + show_icon: bool, + has_hidden_effect: bool, + hidden_effect: Option>, // only if prev is true +} + +impl<'de> Deserialize<'de> for PotionContents { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + impl<'de> de::Visitor<'de> for Visitor { + type Value = PotionContents; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid PotionContents encoded in a byte sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let has_potion_id = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode bool"))?; + + let potion_id: Option; + if has_potion_id { + potion_id = Some( + seq.next_element::()? + .ok_or(de::Error::custom("Failed to decode VarInt"))?, + ); + } else { + potion_id = None + } + + let has_custom_color = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode bool"))?; + let custom_color: Option; + if has_custom_color { + custom_color = Some( + seq.next_element::()? + .ok_or(de::Error::custom("Failed to decode bool"))?, + ); + } else { + custom_color = None; + } + + let number_of_custom_effects = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode VarInt"))?; + + for _ in 0..number_of_custom_effects.0 { + let component_type = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode VarInt!!"))?; + log::info!("dat: {:?}", component_type); + // let s: StructuredComponentType = component_type.into(); + match component_type.0.try_into() { + Ok(StructuredComponentType::PotionContents) => { + log::info!("yesir"); + let has_potion_id = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode potion"))?; + // let potion_id = seq + // .next_element::()? + // .ok_or(de::Error::custom("Failed to decode VarInt"))?; + } + Ok(StructuredComponentType::CustomData) => { + log::info!("uhhuh") + } + Err(_) => log::error!("nooooo"), + // _ => { + // log::error!("nooooo") + // } + } + // let component_data = seq + // .next_element::()? + // .ok_or(de::Error::custom("Unable to parse item"))?; + // array_of_changed_slots.push((slot_number, slot)); + } + + let custom_name = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode VarInt"))?; + + // if num_components_to_add.0 != 0 || num_components_to_remove.0 != 0 { + // return Err(de::Error::custom( + // "Slot components are currently unsupported", + // )); + // } + + log::info!("has_potion: {}", has_potion_id); + if let Some(s) = potion_id.clone() { + log::info!("potion: {}", s.0); + } + log::info!("has_color: {}", has_custom_color); + if let Some(s) = custom_color { + log::info!("custom_color: {}", s); + } + log::info!("num_effects: {}", number_of_custom_effects.0); + + log::info!("num_effects: {}", custom_name); + + Ok(PotionContents { + has_potion_id, + potion_id, + has_custom_color, + custom_color, + number_of_custom_effects, + custom_effects: vec![], + custom_name, + }) + } + } + + deserializer.deserialize_seq(Visitor) + } +} diff --git a/pumpkin-util/src/math/vector3.rs b/pumpkin-util/src/math/vector3.rs index d4e4197e5..2c9820832 100644 --- a/pumpkin-util/src/math/vector3.rs +++ b/pumpkin-util/src/math/vector3.rs @@ -10,6 +10,21 @@ pub struct Vector3 { pub z: T, } +impl Vector3 { + pub fn new_from_euler(yaw: f32, pitch: f32) -> Self { + // TODO: is this because the player is not oriented in the right direction? + let rad_yaw = (yaw + 90.0).to_radians(); + let rad_pitch = (pitch * -1.0).to_radians(); + + let cos_rad_pitch = f32::cos(rad_pitch); + Vector3 { + x: f32::cos(rad_yaw) * cos_rad_pitch, + y: f32::sin(rad_pitch), + z: f32::sin(rad_yaw) * cos_rad_pitch, + } + } +} + impl Vector3 { pub const fn new(x: T, y: T, z: T) -> Self { Vector3 { x, y, z } @@ -27,6 +42,14 @@ impl Vector3 { } } + pub fn add_indv(&self, x: T, y: T, z: T) -> Self { + Vector3 { + x: self.x + x, + y: self.y + y, + z: self.z + z, + } + } + pub fn sub(&self, other: &Vector3) -> Self { Vector3 { x: self.x - other.x, diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 144c58640..8cc2b774f 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -10,11 +10,10 @@ use std::{ use async_trait::async_trait; use crossbeam::atomic::AtomicCell; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; -use pumpkin_data::{ - entity::EntityType, - sound::{Sound, SoundCategory}, -}; -use pumpkin_inventory::player::PlayerInventory; +use pumpkin_data::entity::EntityType; +use pumpkin_data::sound::Sound; +use pumpkin_data::sound::SoundCategory; +use pumpkin_inventory::{player::PlayerInventory, Container}; use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::{ bytebuf::packet_id::Packet, @@ -265,9 +264,9 @@ impl Player { //self.world().level.list_cached(); } - pub async fn attack(&self, victim: &Arc) { + pub async fn attack(&self, victim: &LivingEntity) { let world = self.world(); - let victim_entity = &victim.living_entity.entity; + let victim_entity = &victim.entity; let attacker_entity = &self.living_entity.entity; let config = &ADVANCED_CONFIG.pvp; @@ -317,21 +316,22 @@ impl Player { let pos = victim_entity.pos.load(); - if (config.protect_creative && victim.gamemode.load() == GameMode::Creative) - || !victim.living_entity.check_damage(damage as f32) - { - world - .play_sound( - Sound::EntityPlayerAttackNodamage, - SoundCategory::Players, - &pos, - ) - .await; - return; - } - + // if (config.protect_creative && victim.gamemode.load() == GameMode::Creative) + // || !victim.living_entity.check_damage(damage as f32) + // { + // world + // .play_sound( + // Sound::EntityPlayerAttackNodamage, + // SoundCategory::Players, + // &pos, + // ) + // .await; + // return; + // } + + // change world - .play_sound(Sound::EntityPlayerHurt, SoundCategory::Players, &pos) + .play_sound(Sound::EntityPlayerHurt, SoundCategory::Neutral, &pos) .await; let attack_type = AttackType::new(self, attack_cooldown_progress as f32).await; @@ -343,7 +343,6 @@ impl Player { } victim - .living_entity .damage(damage as f32, 34) // PlayerAttack .await; @@ -357,8 +356,7 @@ impl Player { }; if config.knockback { - combat::handle_knockback(attacker_entity, victim, victim_entity, knockback_strength) - .await; + combat::handle_knockback(attacker_entity, victim, victim_entity, knockback_strength); } if config.hurt_animation { @@ -880,7 +878,10 @@ impl Player { self.handle_use_item_on(SUseItemOn::read(bytebuf)?, server) .await?; } - SUseItem::PACKET_ID => self.handle_use_item(&SUseItem::read(bytebuf)?), + SUseItem::PACKET_ID => { + self.handle_use_item(&SUseItem::read(bytebuf)?, server) + .await + } SCommandSuggestion::PACKET_ID => { self.handle_command_suggestion(SCommandSuggestion::read(bytebuf)?, server) .await; diff --git a/pumpkin/src/net/combat.rs b/pumpkin/src/net/combat.rs index 8125635a4..3c351b22a 100644 --- a/pumpkin/src/net/combat.rs +++ b/pumpkin/src/net/combat.rs @@ -4,6 +4,7 @@ use pumpkin_data::{ particle::Particle, sound::{Sound, SoundCategory}, }; +use pumpkin_inventory::Container; use pumpkin_protocol::{ client::play::{CEntityVelocity, CParticle}, codec::var_int::VarInt, @@ -12,7 +13,7 @@ use pumpkin_util::math::vector3::Vector3; use pumpkin_world::item::ItemStack; use crate::{ - entity::{player::Player, Entity}, + entity::{living::LivingEntity, player::Player, Entity}, world::World, }; @@ -63,9 +64,9 @@ impl AttackType { } } -pub async fn handle_knockback( +pub fn handle_knockback( attacker_entity: &Entity, - victim: &Player, + victim: &LivingEntity, victim_entity: &Entity, strength: f64, ) { @@ -93,7 +94,7 @@ pub async fn handle_knockback( .store(velocity.multiply(0.6, 1.0, 0.6)); victim_entity.velocity.store(saved_velo); - victim.client.send_packet(packet).await; + // victim.client.send_packet(packet).await; } pub async fn spawn_sweep_particle(attacker_entity: &Entity, world: &World, pos: &Vector3) { diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index 3c0a9d4af..025267b86 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -14,6 +14,7 @@ use crate::{ }; use pumpkin_config::ADVANCED_CONFIG; use pumpkin_data::entity::{EntityPose, EntityType}; +use pumpkin_data::sound::Sound; use pumpkin_data::world::CHAT; use pumpkin_inventory::player::PlayerInventory; use pumpkin_inventory::InventoryError; @@ -727,7 +728,7 @@ impl Player { // so we shouldn't kick the player return; } - self.attack(&player_victim).await; + self.attack(&player_victim.living_entity).await; } else if let Some(entity_victim) = entity_victim { // Checks if victim is a living entity if let Some(entity_victim) = entity_victim.get_living_entity() { @@ -735,8 +736,9 @@ impl Player { return; } entity_victim.entity.set_pose(EntityPose::Dying).await; - entity_victim.kill().await; - world.clone().remove_entity(&entity_victim.entity).await; + self.attack(entity_victim).await; + // entity_victim.kill().await; + // world.clone().remove_entity(&entity_victim.entity).await; } // TODO: block entities should be checked here (signs) } else { @@ -996,12 +998,38 @@ impl Player { Ok(()) } - pub fn handle_use_item(&self, _use_item: &SUseItem) { + pub async fn handle_use_item(&self, _use_item: &SUseItem, server: &Arc) { if !self.has_client_loaded() { return; } // TODO: handle packet correctly - log::error!("An item was used(SUseItem), but the packet is not implemented yet"); + // log::error!("An item was used(SUseItem), but the packet is not implemented yet"); + self.world() + .play_sound( + Sound::EntitySplashPotionThrow, + pumpkin_data::sound::SoundCategory::Players, + &self.living_entity.entity.pos.load(), + ) + .await; + + let entity_position = self.living_entity.entity.pos.load(); + let pitch = self.living_entity.entity.pitch.load(); + let yaw = self.living_entity.entity.yaw.load(); + let head = self.living_entity.entity.head_yaw.load(); + log::info!("pitch: {}", pitch); + log::info!("yaw: {}", yaw); + log::info!("head: {}", head); + log::info!("before: {:?}", entity_position.y); + log::info!("seh: {:?}", self.living_entity.entity.standing_eye_height); + let offset_position = entity_position.add_indv( + 0.0, + f64::from(self.living_entity.entity.standing_eye_height), + 0.0, + ); + log::info!("after: {:?}", offset_position.y); + server + .add_potion_entity(EntityType::Potion, offset_position, pitch, yaw) + .await; } pub async fn handle_set_held_item(&self, held: SSetHeldItem) { diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index ceab121a3..ceac27967 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -6,6 +6,7 @@ use pumpkin_data::entity::EntityType; use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_protocol::client::login::CEncryptionRequest; +use pumpkin_protocol::client::play::CLevelEvent; use pumpkin_protocol::{client::config::CPluginMessage, ClientPacket}; use pumpkin_registry::{DimensionType, Registry}; use pumpkin_util::math::boundingbox::{BoundingBox, BoundingBoxSize}; @@ -261,6 +262,66 @@ impl Server { .cloned() } + // TODO: refactor + pub async fn add_potion_entity( + &self, + entity_type: EntityType, + location: Vector3, + pitch: f32, + yaw: f32, + ) -> (Arc, Arc, Uuid) { + let world = &self.worlds.read().await[0]; + let new_uuid = uuid::Uuid::new_v4(); + let entity_id = self.new_entity_id(); + + let bounding_box_size = BoundingBoxSize { + width: 0.6, + height: 1.8, + }; + + let entity = Arc::new(Entity::new( + entity_id, + new_uuid, + world.clone(), + location, + entity_type, + 1.62, + AtomicCell::new(BoundingBox::new_default(&bounding_box_size)), + AtomicCell::new(bounding_box_size), + )); + + entity.set_pos(location); + + let entity_direction = Vector3::new_from_euler(yaw, pitch); + + world.add_entity(&entity.clone(), entity_direction).await; + + let threaded_entity = entity.clone(); + let threaded_location = location; + let threaded_world = world.clone(); + tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await; + // TODO: calculate position of final block, should be based on ticks no separate thread + let landed_block_position = BlockPos(Vector3 { + x: threaded_location.x as i32, + y: (threaded_location.y - 1.62) as i32, + z: threaded_location.z as i32, + }); + threaded_world + .broadcast_packet_all(&CLevelEvent::new( + 2007, + landed_block_position, + 8_364_543, + false, + )) + .await; + threaded_entity.remove().await; + // current_living_entities.remove(&living_entity.entity.entity_uuid); + }); + + (entity, world.clone(), new_uuid) + } + /// Returns the first id with a matching location and block type. If this is used with unique /// blocks, the output will return a random result. pub async fn get_container_id(&self, location: BlockPos, block: Block) -> Option { diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 9968deac6..8c05fd5eb 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -21,8 +21,11 @@ use pumpkin_data::{ sound::{Sound, SoundCategory}, world::WorldEvent, }; -use pumpkin_protocol::client::play::{CBlockUpdate, CDisguisedChatMessage, CRespawn, CWorldEvent}; use pumpkin_protocol::{client::play::CLevelEvent, codec::identifier::Identifier}; +use pumpkin_protocol::{ + client::play::{CBlockUpdate, CDisguisedChatMessage, CRespawn, CWorldEvent}, + codec::var_int::VarInt, +}; use pumpkin_protocol::{ client::play::{ CChunkData, CGameEvent, CLogin, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo, @@ -845,6 +848,32 @@ impl World { current_living_entities.insert(uuid, entity); } + pub async fn add_entity(&self, entity: &Entity, direction: Vector3) { + // TODO: add to list + + log::info!("x: {}", direction.x); + log::info!("y: {}", direction.y); + log::info!("z: {}", direction.z); + + let po = entity.pos.load(); + self.broadcast_packet_all(&CSpawnEntity::new( + VarInt(entity.entity_id), + entity.entity_uuid, + VarInt(EntityType::Potion as i32), + po.x, // + f64::from(cursor_pos.x), + po.y, + po.z, // + f64::from(cursor_pos.z), + 10.0, + 0.0, // head_yaw, + 0.0, // opposite_yaw, + 0.into(), + direction.x, + direction.y, + direction.z, + )) + .await; + } + pub async fn remove_entity(&self, entity: &Entity) { self.broadcast_packet_all(&CRemoveEntities::new(&[entity.entity_id.into()])) .await; From e56dfb5cbf9b647009000c2696bd3c20f64b33d6 Mon Sep 17 00:00:00 2001 From: OfficialKris <37947442+OfficialKris@users.noreply.github.com> Date: Thu, 20 Feb 2025 00:20:38 -0800 Subject: [PATCH 2/6] Various changes --- pumpkin-protocol/src/client/play/spawn_entity.rs | 6 +++--- pumpkin/src/net/packet/play.rs | 2 +- pumpkin/src/server/mod.rs | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pumpkin-protocol/src/client/play/spawn_entity.rs b/pumpkin-protocol/src/client/play/spawn_entity.rs index 8527c734d..554a32ff8 100644 --- a/pumpkin-protocol/src/client/play/spawn_entity.rs +++ b/pumpkin-protocol/src/client/play/spawn_entity.rs @@ -11,7 +11,7 @@ pub struct CSpawnEntity { entity_id: VarInt, #[serde(with = "uuid::serde::compact")] entity_uuid: uuid::Uuid, - typ: VarInt, + entity_type: VarInt, position: Vector3, pitch: u8, // angle yaw: u8, // angle @@ -25,7 +25,7 @@ impl CSpawnEntity { pub fn new( entity_id: VarInt, entity_uuid: uuid::Uuid, - typ: VarInt, + entity_type: VarInt, position: Vector3, pitch: f32, // angle yaw: f32, // angle @@ -36,7 +36,7 @@ impl CSpawnEntity { Self { entity_id, entity_uuid, - typ, + entity_type, position, pitch: (pitch * 256.0 / 360.0).floor() as u8, yaw: (yaw * 256.0 / 360.0).floor() as u8, diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index edf673533..8c038dbe8 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -1149,7 +1149,7 @@ impl Player { log::info!("yaw: {}", yaw); log::info!("head: {}", head); log::info!("before: {:?}", entity_position.y); - log::info!("seh: {:?}", self.living_entity.entity.standing_eye_height); + log::info!("standing eye height: {:?}", self.living_entity.entity.standing_eye_height); let offset_position = entity_position.add_raw( 0.0, f64::from(self.living_entity.entity.standing_eye_height), diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index fdfd0b497..e692ca948 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -3,6 +3,7 @@ use crossbeam::atomic::AtomicCell; use key_store::KeyStore; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_data::entity::EntityType; +use pumpkin_data::world::WorldEvent; use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_protocol::client::login::CEncryptionRequest; @@ -322,7 +323,7 @@ impl Server { }); threaded_world .broadcast_packet_all(&CLevelEvent::new( - 2007, + WorldEvent::InstantSplashPotionSplashed as i32, landed_block_position, 8_364_543, false, From 6679c10da95cddd595717eac11ec756515e6f710 Mon Sep 17 00:00:00 2001 From: OfficialKris <37947442+OfficialKris@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:07:01 -0800 Subject: [PATCH 3/6] Moved to entity and tick system --- .../src/client/play/spawn_entity.rs | 4 +- pumpkin-util/src/math/vector3.rs | 15 ---- pumpkin/src/entity/item.rs | 4 +- pumpkin/src/entity/mob/mod.rs | 2 +- pumpkin/src/entity/mod.rs | 11 ++- pumpkin/src/entity/player.rs | 19 ++++ pumpkin/src/entity/projectile/mod.rs | 87 +++++++++++++++++-- pumpkin/src/item/items/egg.rs | 12 ++- pumpkin/src/item/items/mod.rs | 1 + pumpkin/src/item/items/snowball.rs | 12 ++- pumpkin/src/item/items/splash_potion.rs | 72 +++++++++++++++ pumpkin/src/item/mod.rs | 3 +- pumpkin/src/item/pumpkin_item.rs | 3 + pumpkin/src/item/registry.rs | 16 +++- pumpkin/src/net/packet/play.rs | 61 ++++++------- pumpkin/src/server/mod.rs | 50 ++++++++--- pumpkin/src/world/mod.rs | 41 ++------- 17 files changed, 299 insertions(+), 114 deletions(-) create mode 100644 pumpkin/src/item/items/splash_potion.rs diff --git a/pumpkin-protocol/src/client/play/spawn_entity.rs b/pumpkin-protocol/src/client/play/spawn_entity.rs index 554a32ff8..45ec00911 100644 --- a/pumpkin-protocol/src/client/play/spawn_entity.rs +++ b/pumpkin-protocol/src/client/play/spawn_entity.rs @@ -44,8 +44,8 @@ impl CSpawnEntity { data, velocity: Vector3::new( (velocity.x.clamp(-3.9, 3.9) * 8000.0) as i16, - (velocity.x.clamp(-3.9, 3.9) * 8000.0) as i16, - (velocity.x.clamp(-3.9, 3.9) * 8000.0) as i16, + (velocity.y.clamp(-3.9, 3.9) * 8000.0) as i16, + (velocity.z.clamp(-3.9, 3.9) * 8000.0) as i16, ), } } diff --git a/pumpkin-util/src/math/vector3.rs b/pumpkin-util/src/math/vector3.rs index 997963631..8e0615153 100644 --- a/pumpkin-util/src/math/vector3.rs +++ b/pumpkin-util/src/math/vector3.rs @@ -10,21 +10,6 @@ pub struct Vector3 { pub z: T, } -impl Vector3 { - pub fn new_from_euler(yaw: f32, pitch: f32) -> Self { - // TODO: is this because the player is not oriented in the right direction? - let rad_yaw = (yaw + 90.0).to_radians(); - let rad_pitch = (pitch * -1.0).to_radians(); - - let cos_rad_pitch = f32::cos(rad_pitch); - Vector3 { - x: f32::cos(rad_yaw) * cos_rad_pitch, - y: f32::sin(rad_pitch), - z: f32::sin(rad_yaw) * cos_rad_pitch, - } - } -} - impl Vector3 { pub const fn new(x: T, y: T, z: T) -> Self { Vector3 { x, y, z } diff --git a/pumpkin/src/entity/item.rs b/pumpkin/src/entity/item.rs index 87a0bc6eb..d74feac47 100644 --- a/pumpkin/src/entity/item.rs +++ b/pumpkin/src/entity/item.rs @@ -7,6 +7,8 @@ use pumpkin_protocol::{ }; use pumpkin_world::item::ItemStack; +use crate::server::Server; + use super::{living::LivingEntity, player::Player, Entity, EntityBase}; pub struct ItemEntity { @@ -37,7 +39,7 @@ impl ItemEntity { #[async_trait] impl EntityBase for ItemEntity { - async fn tick(&self) { + async fn tick(&self, _server: &Server) { if self.pickup_delay.load(std::sync::atomic::Ordering::Relaxed) > 0 { self.pickup_delay .fetch_sub(1, std::sync::atomic::Ordering::Relaxed); diff --git a/pumpkin/src/entity/mob/mod.rs b/pumpkin/src/entity/mob/mod.rs index 3b4626f15..c3eb005d9 100644 --- a/pumpkin/src/entity/mob/mod.rs +++ b/pumpkin/src/entity/mob/mod.rs @@ -24,7 +24,7 @@ pub struct MobEntity { #[async_trait] impl EntityBase for MobEntity { - async fn tick(&self) { + async fn tick(&self, _server: &Server) { let mut goals = self.goals.lock().await; for (goal, running) in goals.iter_mut() { if *running { diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index 32535a3fa..4c96e7885 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -29,7 +29,7 @@ use pumpkin_util::math::{ use serde::Serialize; use tokio::sync::RwLock; -use crate::world::World; +use crate::{server::Server, world::World}; pub mod ai; pub mod hunger; @@ -46,17 +46,20 @@ pub type EntityId = i32; #[async_trait] pub trait EntityBase: Send + Sync { /// Gets Called every tick - async fn tick(&self) {} + async fn tick(&self, _server: &Server) {} /// Called when a player collides with the entity async fn on_player_collision(&self, _player: Arc) {} fn get_entity(&self) -> &Entity; fn get_living_entity(&self) -> Option<&LivingEntity>; + async fn on_entity_destroy(&self, _entity: &Entity, _world: &World) {} } /// Represents a not living Entity (e.g. Item, Egg, Snowball...) pub struct Entity { /// A unique identifier for the entity pub entity_id: EntityId, + /// The potential owner for this entity (e.g. the player who threw a snowball) + pub owner_id: Option, /// A persistent, unique identifier for the entity pub entity_uuid: uuid::Uuid, /// The type of entity (e.g., player, zombie, item) @@ -103,6 +106,7 @@ impl Entity { #[expect(clippy::too_many_arguments)] pub fn new( entity_id: EntityId, + owner_id: Option, entity_uuid: uuid::Uuid, world: Arc, position: Vector3, @@ -118,6 +122,7 @@ impl Entity { Self { entity_id, + owner_id, entity_uuid, entity_type, on_ground: AtomicBool::new(false), @@ -383,7 +388,7 @@ impl Entity { #[async_trait] impl EntityBase for Entity { - async fn tick(&self) {} + async fn tick(&self, _server: &Server) {} fn get_entity(&self) -> &Entity { self diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 6fc972c00..20b82ba6f 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -179,6 +179,7 @@ impl Player { Self { living_entity: LivingEntity::new(Entity::new( entity_id, + None, player_uuid, world, Vector3::new(0.0, 0.0, 0.0), @@ -390,6 +391,24 @@ impl Player { if config.swing {} } + pub async fn damage(&self, damage: u32) { + let world = self.world().await; + let config = &ADVANCED_CONFIG.pvp; + let living_entity = self.get_living_entity(); + let entity = self.get_entity(); + + if let Some(living) = living_entity { + living.damage(damage as f32, DamageType::THROWN).await; + } + + if config.hurt_animation { + let entity_id = VarInt(entity.entity_id); + world + .broadcast_packet_all(&CHurtAnimation::new(&entity_id, 0.0)) + .await; + } + } + pub async fn show_title(&self, text: &TextComponent, mode: &TitleMode) { match mode { TitleMode::Title => self.client.send_packet(&CTitleText::new(text)).await, diff --git a/pumpkin/src/entity/projectile/mod.rs b/pumpkin/src/entity/projectile/mod.rs index a316b49a5..46b027fe9 100644 --- a/pumpkin/src/entity/projectile/mod.rs +++ b/pumpkin/src/entity/projectile/mod.rs @@ -1,20 +1,38 @@ -use std::f32::{self}; +use std::{ + f32::{self}, + sync::Arc, +}; +use async_trait::async_trait; +use crossbeam::atomic::AtomicCell; +use pumpkin_data::item::Item; use pumpkin_util::math::vector3::Vector3; -use super::{living::LivingEntity, Entity, EntityBase}; +use crate::server::Server; + +use super::{living::LivingEntity, player::Player, Entity, EntityBase}; pub struct ThrownItemEntity { entity: Entity, + // TODO: remove this for onCollsionWithBlock function + time_to_destruct: u32, + last_tick_time: AtomicCell, + item: Item, } impl ThrownItemEntity { - pub fn new(entity: Entity, owner: &Entity) -> Self { + pub fn new(entity: Entity, owner: &Entity, time_to_destruct: u32, item: Item) -> Self { let mut owner_pos = owner.pos.load(); owner_pos.y = (owner_pos.y + f64::from(owner.standing_eye_height)) - 0.1; entity.pos.store(owner_pos); - Self { entity } + Self { + entity, + time_to_destruct, + last_tick_time: AtomicCell::new(0), + item, + } } + pub fn set_velocity_from( &self, shooter: &Entity, @@ -24,13 +42,18 @@ impl ThrownItemEntity { speed: f32, divergence: f32, ) { + // Convert 3 degree of freedom to radians let yaw_rad = yaw.to_radians(); let pitch_rad = pitch.to_radians(); let roll_rad = (pitch + roll).to_radians(); - let x = -yaw_rad.sin() * pitch_rad.cos(); + // Slight optimization, store cos of pitch, reduces nondeterministic trig + let pitch_rad_cos = pitch_rad.cos(); + + // The player is oriented -90 degrees on the yaw and pitch is -90 to 90 + let x = -yaw_rad.sin() * pitch_rad_cos; let y = -roll_rad.sin(); - let z = yaw_rad.cos() * pitch_rad.cos(); + let z = yaw_rad.cos() * pitch_rad_cos; self.set_velocity( f64::from(x), f64::from(y), @@ -38,6 +61,8 @@ impl ThrownItemEntity { f64::from(speed), f64::from(divergence), ); + + // Add player velocity to velocity let shooter_vel = shooter.velocity.load(); self.entity .velocity @@ -51,6 +76,7 @@ impl ThrownItemEntity { shooter_vel.z, )); } + /// The velocity and rotation will be set to the same direction. pub fn set_velocity(&self, x: f64, y: f64, z: f64, power: f64, uncertainty: f64) { fn next_triangular(mode: f64, deviation: f64) -> f64 { @@ -71,9 +97,47 @@ impl ThrownItemEntity { velocity.y.atan2(len) as f32 * 57.295_776, ); } + + pub fn set_last_time(&self, new_time: u32) { + self.last_tick_time.store(new_time); + } } +#[async_trait] impl EntityBase for ThrownItemEntity { + async fn tick(&self, server: &Server) { + // TODO: Add gravity + + // Moves throwable to next location + let previous_velocity = self.entity.velocity.load(); + let previous_position = self.entity.pos.load(); + + let new_velocity = previous_velocity.multiply(0.99, 0.99, 0.99); + let new_position = previous_position.add(&new_velocity); + + self.entity.velocity.store(new_velocity); + self.entity.set_pos(new_position); + + // Calculate time + self.set_last_time(self.last_tick_time.load() + 1); + if self.last_tick_time.load() > self.time_to_destruct { + let world = self.entity.world.read().await; + server + .item_registry + .on_entity_destroy(&self.item, &self.entity, &world) + .await; + self.entity.remove().await; + // self.entity.world.get_mut().; + } + + // TODO: this should be replaced with function to determine if entity touched ground + if new_velocity.x.abs() < 0.1 && new_velocity.z.abs() < 0.1 { + self.entity.remove().await; + } + + // TODO: need to check if projectile hit a target in between movements + } + fn get_entity(&self) -> &Entity { &self.entity } @@ -81,4 +145,15 @@ impl EntityBase for ThrownItemEntity { fn get_living_entity(&self) -> Option<&LivingEntity> { None } + + async fn on_player_collision(&self, player: Arc) { + // Only damage player if thrower is not the collision + // TODO: does this mean snowballs thrown upwards should do nothing? + if let Some(id) = self.entity.owner_id { + if player.entity_id() != id { + player.damage(1).await; + self.entity.remove().await; + } + } + } } diff --git a/pumpkin/src/item/items/egg.rs b/pumpkin/src/item/items/egg.rs index 8ddbb242a..a6e4c1e37 100644 --- a/pumpkin/src/item/items/egg.rs +++ b/pumpkin/src/item/items/egg.rs @@ -17,7 +17,7 @@ const POWER: f32 = 1.5; #[async_trait] impl PumpkinItem for EggItem { - async fn normal_use(&self, _block: &Item, player: &Player, server: &Server) { + async fn normal_use(&self, _item: &Item, player: &Player, server: &Server) { let position = player.position(); let world = player.world().await; world @@ -28,8 +28,14 @@ impl PumpkinItem for EggItem { ) .await; // TODO: Implement eggs the right way, so there is a chance of spawning chickens - let entity = server.add_entity(position, EntityType::EGG, &world); - let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity); + let entity = server.add_entity_with_owner( + position, + EntityType::EGG, + Some(player.entity_id()), + &world, + ); + let snowball = + ThrownItemEntity::new(entity, &player.living_entity.entity, 3 * 20, Item::EGG); let yaw = player.living_entity.entity.yaw.load(); let pitch = player.living_entity.entity.pitch.load(); snowball.set_velocity_from(&player.living_entity.entity, pitch, yaw, 0.0, POWER, 1.0); diff --git a/pumpkin/src/item/items/mod.rs b/pumpkin/src/item/items/mod.rs index cca21c512..2fbc477c2 100644 --- a/pumpkin/src/item/items/mod.rs +++ b/pumpkin/src/item/items/mod.rs @@ -1,2 +1,3 @@ pub mod egg; pub mod snowball; +pub mod splash_potion; diff --git a/pumpkin/src/item/items/snowball.rs b/pumpkin/src/item/items/snowball.rs index 55f1132ac..52875ae00 100644 --- a/pumpkin/src/item/items/snowball.rs +++ b/pumpkin/src/item/items/snowball.rs @@ -17,7 +17,7 @@ const POWER: f32 = 1.5; #[async_trait] impl PumpkinItem for SnowBallItem { - async fn normal_use(&self, _block: &Item, player: &Player, server: &Server) { + async fn normal_use(&self, _item: &Item, player: &Player, server: &Server) { let position = player.position(); let world = player.world().await; world @@ -27,8 +27,14 @@ impl PumpkinItem for SnowBallItem { &position, ) .await; - let entity = server.add_entity(position, EntityType::SNOWBALL, &world); - let snowball = ThrownItemEntity::new(entity, &player.living_entity.entity); + let entity = server.add_entity_with_owner( + position, + EntityType::SNOWBALL, + Some(player.entity_id()), + &world, + ); + let snowball = + ThrownItemEntity::new(entity, &player.living_entity.entity, 3 * 20, Item::SNOWBALL); let yaw = player.living_entity.entity.yaw.load(); let pitch = player.living_entity.entity.pitch.load(); snowball.set_velocity_from(&player.living_entity.entity, pitch, yaw, 0.0, POWER, 1.0); diff --git a/pumpkin/src/item/items/splash_potion.rs b/pumpkin/src/item/items/splash_potion.rs new file mode 100644 index 000000000..fcf0a4914 --- /dev/null +++ b/pumpkin/src/item/items/splash_potion.rs @@ -0,0 +1,72 @@ +use std::sync::Arc; + +use crate::entity::player::Player; +use crate::entity::projectile::ThrownItemEntity; +use crate::entity::Entity; +use crate::item::pumpkin_item::PumpkinItem; +use crate::server::Server; +use crate::world::World; +use async_trait::async_trait; +use pumpkin_data::entity::EntityType; +use pumpkin_data::item::Item; +use pumpkin_data::sound::Sound; +use pumpkin_data::world::WorldEvent; +use pumpkin_macros::pumpkin_item; +use pumpkin_protocol::client::play::CLevelEvent; +use pumpkin_util::math::position::BlockPos; +use pumpkin_util::math::vector3::Vector3; + +#[pumpkin_item("splash_potion")] +pub struct SplashPotionItem; + +const POWER: f32 = 1.5; + +#[async_trait] +impl PumpkinItem for SplashPotionItem { + async fn normal_use(&self, _item: &Item, player: &Player, server: &Server) { + let position = player.position(); + let world = player.world().await; + world + .play_sound( + Sound::EntitySplashPotionThrow, + pumpkin_data::sound::SoundCategory::Neutral, + &position, + ) + .await; + let entity = server.add_entity_with_owner( + position, + EntityType::POTION, + Some(player.entity_id()), + &world, + ); + let snowball = ThrownItemEntity::new( + entity, + &player.living_entity.entity, + 20, + Item::SPLASH_POTION, + ); + let yaw = player.living_entity.entity.yaw.load(); + let pitch = player.living_entity.entity.pitch.load(); + snowball.set_velocity_from(&player.living_entity.entity, pitch, yaw, 0.0, POWER, 1.0); + world.spawn_entity(Arc::new(snowball)).await; + } + + async fn on_entity_destroy(&self, _item: &Item, entity: &Entity, world: &World) { + // TODO: just use entity.block_pos + let landed_pos = entity.pos.load(); + let landed_block_position = BlockPos(Vector3 { + x: landed_pos.x as i32, + y: landed_pos.y as i32, + z: landed_pos.z as i32, + }); + world + .broadcast_packet_all(&CLevelEvent::new( + WorldEvent::InstantSplashPotionSplashed as i32, + landed_block_position, + // TODO: RGB color as an integer (e.g. 8364543 for #7FA1FF). + 8_364_543, + false, + )) + .await; + } +} diff --git a/pumpkin/src/item/mod.rs b/pumpkin/src/item/mod.rs index db0f38e01..a132ac524 100644 --- a/pumpkin/src/item/mod.rs +++ b/pumpkin/src/item/mod.rs @@ -1,4 +1,4 @@ -use items::{egg::EggItem, snowball::SnowBallItem}; +use items::{egg::EggItem, snowball::SnowBallItem, splash_potion::SplashPotionItem}; use registry::ItemRegistry; use std::sync::Arc; @@ -13,6 +13,7 @@ pub fn default_registry() -> Arc { manager.register(SnowBallItem); manager.register(EggItem); + manager.register(SplashPotionItem); Arc::new(manager) } diff --git a/pumpkin/src/item/pumpkin_item.rs b/pumpkin/src/item/pumpkin_item.rs index 11ba9c6e6..eef49e66e 100644 --- a/pumpkin/src/item/pumpkin_item.rs +++ b/pumpkin/src/item/pumpkin_item.rs @@ -1,5 +1,7 @@ use crate::entity::player::Player; +use crate::entity::Entity; use crate::server::Server; +use crate::world::World; use async_trait::async_trait; use pumpkin_data::item::Item; use pumpkin_util::math::position::BlockPos; @@ -21,4 +23,5 @@ pub trait PumpkinItem: Send + Sync { _server: &Server, ) { } + async fn on_entity_destroy(&self, _item: &Item, _entity: &Entity, _world: &World) {} } diff --git a/pumpkin/src/item/registry.rs b/pumpkin/src/item/registry.rs index 4d7394d8b..87b80579a 100644 --- a/pumpkin/src/item/registry.rs +++ b/pumpkin/src/item/registry.rs @@ -1,5 +1,6 @@ -use crate::entity::player::Player; +use crate::entity::{player::Player, Entity}; use crate::server::Server; +use crate::world::World; use pumpkin_data::item::Item; use pumpkin_util::math::position::BlockPos; use pumpkin_world::block::registry::Block; @@ -19,9 +20,9 @@ impl ItemRegistry { } pub async fn on_use(&self, item: &Item, player: &Player, server: &Server) { - let pumpkin_block = self.get_pumpkin_item(item.id); - if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block.normal_use(item, player, server).await; + let pumpkin_item = self.get_pumpkin_item(item.id); + if let Some(pumpkin_item) = pumpkin_item { + pumpkin_item.normal_use(item, player, server).await; } } @@ -41,6 +42,13 @@ impl ItemRegistry { } } + pub async fn on_entity_destroy(&self, item: &Item, entity: &Entity, world: &World) { + let pumpkin_item = self.get_pumpkin_item(item.id); + if let Some(pumpkin_item) = pumpkin_item { + return pumpkin_item.on_entity_destroy(item, entity, world).await; + } + } + #[must_use] pub fn get_pumpkin_item(&self, item_id: u16) -> Option<&Arc> { self.items.get(&item_id) diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index 8c038dbe8..a646684cb 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -1129,36 +1129,39 @@ impl Player { if !self.has_client_loaded() { return; } - if let Some(_held) = self.inventory().lock().await.held_item() { - // server.item_registry.on_use(&held.item, self, server).await; + if let Some(held) = self.inventory().lock().await.held_item() { + server.item_registry.on_use(&held.item, self, server).await; } - self.world() - .await - .play_sound( - Sound::EntitySplashPotionThrow, - pumpkin_data::sound::SoundCategory::Players, - &self.living_entity.entity.pos.load(), - ) - .await; - - let entity_position = self.living_entity.entity.pos.load(); - let pitch = self.living_entity.entity.pitch.load(); - let yaw = self.living_entity.entity.yaw.load(); - let head = self.living_entity.entity.head_yaw.load(); - log::info!("pitch: {}", pitch); - log::info!("yaw: {}", yaw); - log::info!("head: {}", head); - log::info!("before: {:?}", entity_position.y); - log::info!("standing eye height: {:?}", self.living_entity.entity.standing_eye_height); - let offset_position = entity_position.add_raw( - 0.0, - f64::from(self.living_entity.entity.standing_eye_height), - 0.0, - ); - log::info!("after: {:?}", offset_position.y); - server - .add_potion_entity(EntityType::POTION, offset_position, pitch, yaw) - .await; + // self.world() + // .await + // .play_sound( + // Sound::EntitySplashPotionThrow, + // pumpkin_data::sound::SoundCategory::Players, + // &self.living_entity.entity.pos.load(), + // ) + // .await; + + // let entity_position = self.living_entity.entity.pos.load(); + // let pitch = self.living_entity.entity.pitch.load(); + // let yaw = self.living_entity.entity.yaw.load(); + // let head = self.living_entity.entity.head_yaw.load(); + // log::info!("pitch: {}", pitch); + // log::info!("yaw: {}", yaw); + // log::info!("head: {}", head); + // log::info!("before: {:?}", entity_position.y); + // log::info!( + // "standing eye height: {:?}", + // self.living_entity.entity.standing_eye_height + // ); + // let offset_position = entity_position.add_raw( + // 0.0, + // f64::from(self.living_entity.entity.standing_eye_height), + // 0.0, + // ); + // log::info!("after: {:?}", offset_position.y); + // server + // .add_potion_entity(EntityType::POTION, offset_position, pitch, yaw) + // .await; } pub async fn handle_set_held_item(&self, held: SSetHeldItem) { diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index e692ca948..834e90477 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -213,18 +213,23 @@ impl Server { } /// Adds a new living entity to the server. This does not Spawn the entity + /// This also adds an optional owner to the entity. /// - /// # Returns + /// # Arguments /// - /// A tuple containing: + /// * `position`: The position in the world where the entity will be created. + /// * `entity_type`: The type of the entity. This also controls the bounding box size. + /// * `owner_id`: An `Option` containing the entity of the owner, if it exists. + /// * `world`: A reference to the world which the entity will be added to. /// - /// - `Arc`: A reference to the newly created living entity. - /// - `Arc`: A reference to the world that the living entity was added to. - /// - `Uuid`: The uuid of the newly created living entity to be used to send to the client. - pub fn add_entity( + /// # Returns + /// + /// An `Entity` containing the newly created entity. + pub fn add_entity_with_owner( &self, position: Vector3, entity_type: EntityType, + owner_id: Option, world: &Arc, ) -> Entity { let entity_id = self.new_entity_id(); @@ -238,6 +243,7 @@ impl Server { let new_uuid = uuid::Uuid::new_v4(); Entity::new( entity_id, + owner_id, new_uuid, world.clone(), position, @@ -254,6 +260,26 @@ impl Server { ) } + /// Adds a new living entity to the server. This does not Spawn the entity + /// + /// # Arguments + /// + /// * `position`: The position in the world where the entity will be created. + /// * `entity_type`: The type of the entity. This also controls the bounding box size. + /// * `world`: A reference to the world which the entity will be added to. + /// + /// # Returns + /// + /// An `Entity` containing the newly created entity. + pub fn add_entity( + &self, + position: Vector3, + entity_type: EntityType, + world: &Arc, + ) -> Entity { + self.add_entity_with_owner(position, entity_type, None, world) + } + pub async fn try_get_container( &self, player_id: EntityId, @@ -271,8 +297,8 @@ impl Server { &self, entity_type: EntityType, location: Vector3, - pitch: f32, - yaw: f32, + _pitch: f32, + _yaw: f32, ) -> (Arc, Arc, uuid::Uuid) { let world = &self.worlds.read().await[0]; let new_uuid = uuid::Uuid::new_v4(); @@ -294,6 +320,7 @@ impl Server { let entity = Arc::new(Entity::new( entity_id, + None, new_uuid, world.clone(), location, @@ -306,9 +333,9 @@ impl Server { entity.set_pos(location); - let entity_direction = Vector3::new_from_euler(yaw, pitch); + // let entity_direction = Vector3::new_from_euler(yaw.into(), pitch.into()); - world.add_entity(&entity.clone(), entity_direction).await; + // world.add_entity(&entity.clone(), entity_direction).await; let threaded_entity = entity.clone(); let threaded_location = location; @@ -325,6 +352,7 @@ impl Server { .broadcast_packet_all(&CLevelEvent::new( WorldEvent::InstantSplashPotionSplashed as i32, landed_block_position, + // TODO: RGB color as an integer (e.g. 8364543 for #7FA1FF). 8_364_543, false, )) @@ -552,7 +580,7 @@ impl Server { async fn tick(&self) { for world in self.worlds.read().await.iter() { - world.tick().await; + world.tick(self).await; } } } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index cd4715d81..5aa7188bd 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -29,11 +29,8 @@ use pumpkin_data::{ }; use pumpkin_macros::send_cancellable; use pumpkin_protocol::client::play::CSetBlockDestroyStage; +use pumpkin_protocol::client::play::{CBlockUpdate, CDisguisedChatMessage, CRespawn, CWorldEvent}; use pumpkin_protocol::{client::play::CLevelEvent, codec::identifier::Identifier}; -use pumpkin_protocol::{ - client::play::{CBlockUpdate, CDisguisedChatMessage, CRespawn, CWorldEvent}, - codec::var_int::VarInt, -}; use pumpkin_protocol::{ client::play::{ CChunkData, CGameEvent, CLogin, CPlayerInfoUpdate, CRemoveEntities, CRemovePlayerInfo, @@ -264,7 +261,7 @@ impl World { .await; } - pub async fn tick(&self) { + pub async fn tick(&self, server: &Server) { // world ticks { let mut level_time = self.level_time.lock().await; @@ -288,9 +285,9 @@ impl World { // entities tick for entity in entities_to_tick { - entity.tick().await; + entity.tick(server).await; // this boolean thing prevents deadlocks, since we lock players we can't broadcast packets - let mut collied_player = None; + let mut collided_player = None; for player in self.players.read().await.values() { if player .living_entity @@ -299,11 +296,11 @@ impl World { .load() .intersects(&entity.get_entity().bounding_box.load()) { - collied_player = Some(player.clone()); + collided_player = Some(player.clone()); break; } } - if let Some(player) = collied_player { + if let Some(player) = collided_player { entity.on_player_collision(player).await; } } @@ -983,32 +980,6 @@ impl World { current_living_entities.insert(base_entity.entity_uuid, entity); } - pub async fn add_entity(&self, entity: &Entity, direction: Vector3) { - // TODO: add to list - - log::info!("x: {}", direction.x); - log::info!("y: {}", direction.y); - log::info!("z: {}", direction.z); - - let po = entity.pos.load(); - self.broadcast_packet_all(&CSpawnEntity::new( - VarInt(entity.entity_id), - entity.entity_uuid, - VarInt(i32::from(EntityType::POTION.id)), - Vector3::new(po.x, po.y, po.z), // + f64::from(cursor_pos.x), - // po.y, - // po.z, // + f64::from(cursor_pos.z), - 10.0, - 0.0, // head_yaw, - 0.0, // opposite_yaw, - 0.into(), - Vector3::new(direction.x.into(), direction.y.into(), direction.z.into()), - // direction.y, - // direction.z, - )) - .await; - } - pub async fn remove_entity(&self, entity: &Entity) { self.entities.write().await.remove(&entity.entity_uuid); self.broadcast_packet_all(&CRemoveEntities::new(&[entity.entity_id.into()])) From d845f7916e381e69475d1176f191cb81308dd6a4 Mon Sep 17 00:00:00 2001 From: OfficialKris <37947442+OfficialKris@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:21:06 -0800 Subject: [PATCH 4/6] Fix spelling --- pumpkin/src/entity/projectile/mod.rs | 2 +- pumpkin/src/item/items/splash_potion.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pumpkin/src/entity/projectile/mod.rs b/pumpkin/src/entity/projectile/mod.rs index 979102edf..d3d4351ee 100644 --- a/pumpkin/src/entity/projectile/mod.rs +++ b/pumpkin/src/entity/projectile/mod.rs @@ -14,7 +14,7 @@ use super::{Entity, EntityBase, living::LivingEntity, player::Player}; pub struct ThrownItemEntity { entity: Entity, - // TODO: remove this for onCollsionWithBlock function + // TODO: remove this for onCollisionWithBlock function time_to_destruct: u32, last_tick_time: AtomicCell, item: Item, diff --git a/pumpkin/src/item/items/splash_potion.rs b/pumpkin/src/item/items/splash_potion.rs index 46fb35ba8..0c052668b 100644 --- a/pumpkin/src/item/items/splash_potion.rs +++ b/pumpkin/src/item/items/splash_potion.rs @@ -19,7 +19,7 @@ use pumpkin_util::math::vector3::Vector3; #[pumpkin_item("splash_potion")] pub struct SplashPotionItem; -const POWER: f32 = 1.5; +const POWER: f32 = 1.0; #[async_trait] impl PumpkinItem for SplashPotionItem { From 6ba719269670c977f58119f0551c8f587617874e Mon Sep 17 00:00:00 2001 From: OfficialKris <37947442+OfficialKris@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:32:15 -0800 Subject: [PATCH 5/6] Added gravity --- pumpkin/src/entity/projectile/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pumpkin/src/entity/projectile/mod.rs b/pumpkin/src/entity/projectile/mod.rs index d3d4351ee..3e907fad2 100644 --- a/pumpkin/src/entity/projectile/mod.rs +++ b/pumpkin/src/entity/projectile/mod.rs @@ -106,13 +106,14 @@ impl ThrownItemEntity { #[async_trait] impl EntityBase for ThrownItemEntity { async fn tick(&self, server: &Server) { - // TODO: Add gravity + // Gravity for thrown potion, projectile is 0.03; + let gravity = Vector3::new(0.0, -0.05, 0.0); // Moves throwable to next location let previous_velocity = self.entity.velocity.load(); let previous_position = self.entity.pos.load(); - let new_velocity = previous_velocity.multiply(0.99, 0.99, 0.99); + let new_velocity = previous_velocity.add(&gravity).multiply(0.99, 0.99, 0.99); let new_position = previous_position.add(&new_velocity); self.entity.velocity.store(new_velocity); @@ -149,6 +150,7 @@ impl EntityBase for ThrownItemEntity { async fn on_player_collision(&self, player: Arc) { // Only damage player if thrower is not the collision // TODO: does this mean snowballs thrown upwards should do nothing? + // TODO: Add on_destroy function here if let Some(id) = self.entity.owner_id { if player.entity_id() != id { player.damage(1).await; From 8111724c5c42991b13d85dafa07f8bfc4374e3e7 Mon Sep 17 00:00:00 2001 From: OfficialKris <37947442+OfficialKris@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:41:40 -0800 Subject: [PATCH 6/6] Removed unneeded code --- pumpkin/src/net/packet/play.rs | 30 -------------- pumpkin/src/server/mod.rs | 74 ---------------------------------- 2 files changed, 104 deletions(-) diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index 425f2bb59..2f9a6033c 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -1131,36 +1131,6 @@ impl Player { if let Some(held) = self.inventory().lock().await.held_item() { server.item_registry.on_use(&held.item, self, server).await; } - // self.world() - // .await - // .play_sound( - // Sound::EntitySplashPotionThrow, - // pumpkin_data::sound::SoundCategory::Players, - // &self.living_entity.entity.pos.load(), - // ) - // .await; - - // let entity_position = self.living_entity.entity.pos.load(); - // let pitch = self.living_entity.entity.pitch.load(); - // let yaw = self.living_entity.entity.yaw.load(); - // let head = self.living_entity.entity.head_yaw.load(); - // log::info!("pitch: {}", pitch); - // log::info!("yaw: {}", yaw); - // log::info!("head: {}", head); - // log::info!("before: {:?}", entity_position.y); - // log::info!( - // "standing eye height: {:?}", - // self.living_entity.entity.standing_eye_height - // ); - // let offset_position = entity_position.add_raw( - // 0.0, - // f64::from(self.living_entity.entity.standing_eye_height), - // 0.0, - // ); - // log::info!("after: {:?}", offset_position.y); - // server - // .add_potion_entity(EntityType::POTION, offset_position, pitch, yaw) - // .await; } pub async fn handle_set_held_item(&self, held: SSetHeldItem) { diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 21bb59669..4357420df 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -3,11 +3,9 @@ use crossbeam::atomic::AtomicCell; use key_store::KeyStore; use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG}; use pumpkin_data::entity::EntityType; -use pumpkin_data::world::WorldEvent; use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_protocol::client::login::CEncryptionRequest; -use pumpkin_protocol::client::play::CLevelEvent; use pumpkin_protocol::{ClientPacket, client::config::CPluginMessage}; use pumpkin_registry::{DimensionType, Registry}; use pumpkin_util::math::boundingbox::{BoundingBox, EntityDimensions}; @@ -292,78 +290,6 @@ impl Server { .cloned() } - // TODO: refactor - pub async fn add_potion_entity( - &self, - entity_type: EntityType, - location: Vector3, - _pitch: f32, - _yaw: f32, - ) -> (Arc, Arc, uuid::Uuid) { - let world = &self.worlds.read().await[0]; - let new_uuid = uuid::Uuid::new_v4(); - let entity_id = self.new_entity_id(); - - // TODO: this should be resolved to a integer using a macro when calling this function - let bounding_box_size = EntityDimensions { - width: entity_type.dimension[0], - height: entity_type.dimension[1], - }; - - let bounding_box = - BoundingBox::new_from_pos(location.x, location.y, location.z, &bounding_box_size); - - // let bounding_box_size = BoundingBoxSize { - // width: 0.6, - // height: 1.8, - // }; - - let entity = Arc::new(Entity::new( - entity_id, - None, - new_uuid, - world.clone(), - location, - entity_type, - 1.62, - AtomicCell::new(bounding_box), - AtomicCell::new(bounding_box_size), - true, - )); - - entity.set_pos(location); - - // let entity_direction = Vector3::new_from_euler(yaw.into(), pitch.into()); - - // world.add_entity(&entity.clone(), entity_direction).await; - - let threaded_entity = entity.clone(); - let threaded_location = location; - let threaded_world = world.clone(); - tokio::spawn(async move { - tokio::time::sleep(tokio::time::Duration::from_millis(2000)).await; - // TODO: calculate position of final block, should be based on ticks no separate thread - let landed_block_position = BlockPos(Vector3 { - x: threaded_location.x as i32, - y: (threaded_location.y - 1.62) as i32, - z: threaded_location.z as i32, - }); - threaded_world - .broadcast_packet_all(&CLevelEvent::new( - WorldEvent::InstantSplashPotionSplashed as i32, - landed_block_position, - // TODO: RGB color as an integer (e.g. 8364543 for #7FA1FF). - 8_364_543, - false, - )) - .await; - threaded_entity.remove().await; - // current_living_entities.remove(&living_entity.entity.entity_uuid); - }); - - (entity, world.clone(), new_uuid) - } - /// Returns the first id with a matching location and block type. If this is used with unique /// blocks, the output will return a random result. pub async fn get_container_id(&self, location: BlockPos, block: Block) -> Option {