From 79ad1c40f3cf06f6c0af2ecbdd53bae9b6579ca1 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Mon, 10 Feb 2025 23:32:04 +0100 Subject: [PATCH] Make non Player entities attackable --- pumpkin-data/build/damage_type.rs | 32 ++++++++++++ .../noise_router/chunk_density_function.rs | 2 +- pumpkin/src/entity/combat.rs | 27 ++++------ pumpkin/src/entity/item.rs | 2 +- pumpkin/src/entity/player.rs | 51 ++++++++++++------- pumpkin/src/net/packet/play.rs | 47 ++++++++++------- 6 files changed, 104 insertions(+), 57 deletions(-) diff --git a/pumpkin-data/build/damage_type.rs b/pumpkin-data/build/damage_type.rs index 6f0b51f8a..f0b6aa570 100644 --- a/pumpkin-data/build/damage_type.rs +++ b/pumpkin-data/build/damage_type.rs @@ -15,10 +15,22 @@ struct DamageTypeEntry { pub struct DamageTypeData { death_message_type: Option, exhaustion: f32, + effects: Option, message_id: String, scaling: DamageScaling, } +#[derive(Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum DamageEffects { + Hurt, + Thorns, + Drowning, + Burning, + Poking, + Freezing, +} + #[derive(Deserialize, Debug)] #[serde(rename_all = "snake_case")] pub enum DamageScaling { @@ -59,6 +71,14 @@ pub(crate) fn build() -> TokenStream { None => quote! { None }, }; + let effects = match &data.effects { + Some(msg) => { + let msg_ident = Ident::new(&format!("{:?}", msg), proc_macro2::Span::call_site()); + quote! { Some(DamageEffects::#msg_ident) } + } + None => quote! { None }, + }; + let exhaustion = data.exhaustion; let message_id = &data.message_id; let scaling_ident = Ident::new( @@ -72,6 +92,7 @@ pub(crate) fn build() -> TokenStream { pub const #const_ident: DamageType = DamageType { death_message_type: #death_message_type, exhaustion: #exhaustion, + effects: #effects, message_id: #message_id, scaling: #scaling, id: #id_lit, @@ -93,6 +114,7 @@ pub(crate) fn build() -> TokenStream { pub struct DamageType { pub death_message_type: Option, pub exhaustion: f32, + pub effects: Option, pub message_id: &'static str, pub scaling: DamageScaling, pub id: u32, @@ -105,6 +127,16 @@ pub(crate) fn build() -> TokenStream { IntentionalGameDesign, } + #[derive(Clone, Copy, Debug, PartialEq)] + pub enum DamageEffects { + Hurt, + Thorns, + Drowning, + Burning, + Poking, + Freezing, + } + #[derive(Clone, Copy, Debug, PartialEq)] pub enum DamageScaling { Never, diff --git a/pumpkin-world/src/generation/noise_router/chunk_density_function.rs b/pumpkin-world/src/generation/noise_router/chunk_density_function.rs index eda610a18..036cd0e72 100644 --- a/pumpkin-world/src/generation/noise_router/chunk_density_function.rs +++ b/pumpkin-world/src/generation/noise_router/chunk_density_function.rs @@ -417,7 +417,7 @@ impl Cache2D { pub fn new(input_index: usize, min_value: f64, max_value: f64) -> Self { Self { input_index, - // I know this is because theres is definately world coords that are this marker, but this + // I know this is because theres is definitely world coords that are this marker, but this // is how vanilla does it, so I'm going to for pairity last_sample_column: chunk_pos::MARKER, last_sample_result: Default::default(), diff --git a/pumpkin/src/entity/combat.rs b/pumpkin/src/entity/combat.rs index a509289e6..a9f57d98b 100644 --- a/pumpkin/src/entity/combat.rs +++ b/pumpkin/src/entity/combat.rs @@ -61,23 +61,18 @@ impl AttackType { } } -pub async fn handle_knockback( - attacker_entity: &Entity, - victim: &Player, - victim_entity: &Entity, - strength: f64, -) { - let yaw = attacker_entity.yaw.load(); +pub async fn handle_knockback(attacker: &Entity, world: &World, victim: &Entity, strength: f64) { + let yaw = attacker.yaw.load(); - let saved_velo = victim_entity.velocity.load(); - victim_entity.knockback( + let saved_velo = victim.velocity.load(); + victim.knockback( strength * 0.5, f64::from((yaw.to_radians()).sin()), f64::from(-(yaw.to_radians()).cos()), ); - let entity_id = VarInt(victim_entity.entity_id); - let victim_velocity = victim_entity.velocity.load(); + let entity_id = VarInt(victim.entity_id); + let victim_velocity = victim.velocity.load(); let packet = &CEntityVelocity::new( &entity_id, @@ -85,13 +80,11 @@ pub async fn handle_knockback( victim_velocity.y, victim_velocity.z, ); - let velocity = attacker_entity.velocity.load(); - attacker_entity - .velocity - .store(velocity.multiply(0.6, 1.0, 0.6)); + let velocity = attacker.velocity.load(); + attacker.velocity.store(velocity.multiply(0.6, 1.0, 0.6)); - victim_entity.velocity.store(saved_velo); - victim.client.send_packet(packet).await; + victim.velocity.store(saved_velo); + world.broadcast_packet_all(packet).await; } pub async fn spawn_sweep_particle(attacker_entity: &Entity, world: &World, pos: &Vector3) { diff --git a/pumpkin/src/entity/item.rs b/pumpkin/src/entity/item.rs index 53bac1b3d..87a0bc6eb 100644 --- a/pumpkin/src/entity/item.rs +++ b/pumpkin/src/entity/item.rs @@ -30,7 +30,7 @@ impl ItemEntity { } pub async fn send_meta_packet(&self) { self.entity - .send_meta_data(Metadata::new(8, MetaDataType::ItemStack, self.item.clone())) + .send_meta_data(Metadata::new(8, MetaDataType::ItemStack, &self.item)) .await; } } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 02ba51aa3..022310d0c 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -65,7 +65,7 @@ use super::{ combat::{self, player_attack_sound, AttackType}, hunger::HungerManager, item::ItemEntity, - Entity, EntityId, NBTStorage, + Entity, EntityBase, EntityId, NBTStorage, }; use crate::{ command::{client_suggestions, dispatcher::CommandDispatcher}, @@ -271,9 +271,10 @@ impl Player { //self.world().level.list_cached(); } - pub async fn attack(&self, victim: &Arc) { + pub async fn attack(&self, victim: Arc) { let world = self.world(); - let victim_entity = &victim.living_entity.entity; + let victim_entity = victim.get_entity(); + let victim_living_entity = victim.get_living_entity(); let attacker_entity = &self.living_entity.entity; let config = &ADVANCED_CONFIG.pvp; @@ -321,17 +322,17 @@ 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 let Some(living) = victim_living_entity { + if !living.check_damage(damage as f32) { + world + .play_sound( + Sound::EntityPlayerAttackNodamage, + SoundCategory::Players, + &pos, + ) + .await; + return; + } } world @@ -346,10 +347,11 @@ impl Player { damage *= 1.5; } - victim - .living_entity - .damage(damage as f32, DamageType::PLAYER_ATTACK) - .await; + if let Some(living) = victim_living_entity { + living + .damage(damage as f32, DamageType::PLAYER_ATTACK) + .await; + } let mut knockback_strength = 1.0; match attack_type { @@ -361,7 +363,7 @@ impl Player { }; if config.knockback { - combat::handle_knockback(attacker_entity, victim, victim_entity, knockback_strength) + combat::handle_knockback(attacker_entity, world, victim_entity, knockback_strength) .await; } @@ -913,6 +915,17 @@ impl NBTStorage for Player { } } +#[async_trait] +impl EntityBase for Player { + fn get_entity(&self) -> &Entity { + &self.living_entity.entity + } + + fn get_living_entity(&self) -> Option<&LivingEntity> { + Some(&self.living_entity) + } +} + impl Player { pub async fn process_packets(self: &Arc, server: &Arc) { let mut packets = self.client.client_packets_queue.lock().await; diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index c37235521..7ee7a00d4 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -13,8 +13,10 @@ use crate::{ world::chunker, }; use pumpkin_config::ADVANCED_CONFIG; -use pumpkin_data::entity::{entity_from_egg, EntityPose, EntityType}; +use pumpkin_data::entity::{entity_from_egg, EntityType}; use pumpkin_data::item::Item; +use pumpkin_data::sound::Sound; +use pumpkin_data::sound::SoundCategory; use pumpkin_data::world::CHAT; use pumpkin_inventory::player::PlayerInventory; use pumpkin_inventory::InventoryError; @@ -741,27 +743,40 @@ impl Player { return; } + // TODO: set as camera entity when specator + let world = &entity.world; let player_victim = world.get_player_by_id(entity_id.0).await; - let entity_victim = world.get_entity_by_id(entity_id.0).await; + if entity_id.0 == self.entity_id() { + // this can't be triggered from a non-modded client. + self.kick(TextComponent::translate( + "multiplayer.disconnect.invalid_entity_attacked", + [].into(), + )) + .await; + return; + } if let Some(player_victim) = player_victim { if player_victim.living_entity.health.load() <= 0.0 { // you can trigger this from a non-modded / innocent client client, // so we shouldn't kick the player return; } - self.attack(&player_victim).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() { - if entity_victim.health.load() <= 0.0 { - return; - } - entity_victim.entity.set_pose(EntityPose::Dying).await; - entity_victim.kill().await; - world.clone().remove_entity(&entity_victim.entity).await; + if config.protect_creative + && player_victim.gamemode.load() == GameMode::Creative + { + world + .play_sound( + Sound::EntityPlayerAttackNodamage, + SoundCategory::Players, + &player_victim.position(), + ) + .await; + return; } - // TODO: block entities should be checked here (signs) + self.attack(player_victim).await; + } else if let Some(entity_victim) = world.get_entity_by_id(entity_id.0).await { + self.attack(entity_victim).await; } else { log::error!( "Player id {} interacted with entity id {} which was not found.", @@ -775,12 +790,6 @@ impl Player { .await; return; }; - - if entity_id.0 == self.entity_id() { - // this, however, can't be triggered from a non-modded client. - self.kick(TextComponent::text("You can't attack yourself")) - .await; - } } ActionType::Interact | ActionType::InteractAt => { log::debug!("todo");