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;