Make non Player entities attackable

This commit is contained in:
Alexander Medvedev 2025-02-10 23:32:04 +01:00
parent 8d2ca8e404
commit 79ad1c40f3
6 changed files with 104 additions and 57 deletions

View File

@ -15,10 +15,22 @@ struct DamageTypeEntry {
pub struct DamageTypeData { pub struct DamageTypeData {
death_message_type: Option<DeathMessageType>, death_message_type: Option<DeathMessageType>,
exhaustion: f32, exhaustion: f32,
effects: Option<DamageEffects>,
message_id: String, message_id: String,
scaling: DamageScaling, scaling: DamageScaling,
} }
#[derive(Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum DamageEffects {
Hurt,
Thorns,
Drowning,
Burning,
Poking,
Freezing,
}
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum DamageScaling { pub enum DamageScaling {
@ -59,6 +71,14 @@ pub(crate) fn build() -> TokenStream {
None => quote! { None }, 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 exhaustion = data.exhaustion;
let message_id = &data.message_id; let message_id = &data.message_id;
let scaling_ident = Ident::new( let scaling_ident = Ident::new(
@ -72,6 +92,7 @@ pub(crate) fn build() -> TokenStream {
pub const #const_ident: DamageType = DamageType { pub const #const_ident: DamageType = DamageType {
death_message_type: #death_message_type, death_message_type: #death_message_type,
exhaustion: #exhaustion, exhaustion: #exhaustion,
effects: #effects,
message_id: #message_id, message_id: #message_id,
scaling: #scaling, scaling: #scaling,
id: #id_lit, id: #id_lit,
@ -93,6 +114,7 @@ pub(crate) fn build() -> TokenStream {
pub struct DamageType { pub struct DamageType {
pub death_message_type: Option<DeathMessageType>, pub death_message_type: Option<DeathMessageType>,
pub exhaustion: f32, pub exhaustion: f32,
pub effects: Option<DamageEffects>,
pub message_id: &'static str, pub message_id: &'static str,
pub scaling: DamageScaling, pub scaling: DamageScaling,
pub id: u32, pub id: u32,
@ -105,6 +127,16 @@ pub(crate) fn build() -> TokenStream {
IntentionalGameDesign, IntentionalGameDesign,
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DamageEffects {
Hurt,
Thorns,
Drowning,
Burning,
Poking,
Freezing,
}
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum DamageScaling { pub enum DamageScaling {
Never, Never,

View File

@ -417,7 +417,7 @@ impl Cache2D {
pub fn new(input_index: usize, min_value: f64, max_value: f64) -> Self { pub fn new(input_index: usize, min_value: f64, max_value: f64) -> Self {
Self { Self {
input_index, 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 // is how vanilla does it, so I'm going to for pairity
last_sample_column: chunk_pos::MARKER, last_sample_column: chunk_pos::MARKER,
last_sample_result: Default::default(), last_sample_result: Default::default(),

View File

@ -61,23 +61,18 @@ impl AttackType {
} }
} }
pub async fn handle_knockback( pub async fn handle_knockback(attacker: &Entity, world: &World, victim: &Entity, strength: f64) {
attacker_entity: &Entity, let yaw = attacker.yaw.load();
victim: &Player,
victim_entity: &Entity,
strength: f64,
) {
let yaw = attacker_entity.yaw.load();
let saved_velo = victim_entity.velocity.load(); let saved_velo = victim.velocity.load();
victim_entity.knockback( victim.knockback(
strength * 0.5, strength * 0.5,
f64::from((yaw.to_radians()).sin()), f64::from((yaw.to_radians()).sin()),
f64::from(-(yaw.to_radians()).cos()), f64::from(-(yaw.to_radians()).cos()),
); );
let entity_id = VarInt(victim_entity.entity_id); let entity_id = VarInt(victim.entity_id);
let victim_velocity = victim_entity.velocity.load(); let victim_velocity = victim.velocity.load();
let packet = &CEntityVelocity::new( let packet = &CEntityVelocity::new(
&entity_id, &entity_id,
@ -85,13 +80,11 @@ pub async fn handle_knockback(
victim_velocity.y, victim_velocity.y,
victim_velocity.z, victim_velocity.z,
); );
let velocity = attacker_entity.velocity.load(); let velocity = attacker.velocity.load();
attacker_entity attacker.velocity.store(velocity.multiply(0.6, 1.0, 0.6));
.velocity
.store(velocity.multiply(0.6, 1.0, 0.6));
victim_entity.velocity.store(saved_velo); victim.velocity.store(saved_velo);
victim.client.send_packet(packet).await; world.broadcast_packet_all(packet).await;
} }
pub async fn spawn_sweep_particle(attacker_entity: &Entity, world: &World, pos: &Vector3<f64>) { pub async fn spawn_sweep_particle(attacker_entity: &Entity, world: &World, pos: &Vector3<f64>) {

View File

@ -30,7 +30,7 @@ impl ItemEntity {
} }
pub async fn send_meta_packet(&self) { pub async fn send_meta_packet(&self) {
self.entity self.entity
.send_meta_data(Metadata::new(8, MetaDataType::ItemStack, self.item.clone())) .send_meta_data(Metadata::new(8, MetaDataType::ItemStack, &self.item))
.await; .await;
} }
} }

View File

@ -65,7 +65,7 @@ use super::{
combat::{self, player_attack_sound, AttackType}, combat::{self, player_attack_sound, AttackType},
hunger::HungerManager, hunger::HungerManager,
item::ItemEntity, item::ItemEntity,
Entity, EntityId, NBTStorage, Entity, EntityBase, EntityId, NBTStorage,
}; };
use crate::{ use crate::{
command::{client_suggestions, dispatcher::CommandDispatcher}, command::{client_suggestions, dispatcher::CommandDispatcher},
@ -271,9 +271,10 @@ impl Player {
//self.world().level.list_cached(); //self.world().level.list_cached();
} }
pub async fn attack(&self, victim: &Arc<Self>) { pub async fn attack(&self, victim: Arc<dyn EntityBase>) {
let world = self.world(); 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 attacker_entity = &self.living_entity.entity;
let config = &ADVANCED_CONFIG.pvp; let config = &ADVANCED_CONFIG.pvp;
@ -321,17 +322,17 @@ impl Player {
let pos = victim_entity.pos.load(); let pos = victim_entity.pos.load();
if (config.protect_creative && victim.gamemode.load() == GameMode::Creative) if let Some(living) = victim_living_entity {
|| !victim.living_entity.check_damage(damage as f32) if !living.check_damage(damage as f32) {
{ world
world .play_sound(
.play_sound( Sound::EntityPlayerAttackNodamage,
Sound::EntityPlayerAttackNodamage, SoundCategory::Players,
SoundCategory::Players, &pos,
&pos, )
) .await;
.await; return;
return; }
} }
world world
@ -346,10 +347,11 @@ impl Player {
damage *= 1.5; damage *= 1.5;
} }
victim if let Some(living) = victim_living_entity {
.living_entity living
.damage(damage as f32, DamageType::PLAYER_ATTACK) .damage(damage as f32, DamageType::PLAYER_ATTACK)
.await; .await;
}
let mut knockback_strength = 1.0; let mut knockback_strength = 1.0;
match attack_type { match attack_type {
@ -361,7 +363,7 @@ impl Player {
}; };
if config.knockback { if config.knockback {
combat::handle_knockback(attacker_entity, victim, victim_entity, knockback_strength) combat::handle_knockback(attacker_entity, world, victim_entity, knockback_strength)
.await; .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 { impl Player {
pub async fn process_packets(self: &Arc<Self>, server: &Arc<Server>) { pub async fn process_packets(self: &Arc<Self>, server: &Arc<Server>) {
let mut packets = self.client.client_packets_queue.lock().await; let mut packets = self.client.client_packets_queue.lock().await;

View File

@ -13,8 +13,10 @@ use crate::{
world::chunker, world::chunker,
}; };
use pumpkin_config::ADVANCED_CONFIG; 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::item::Item;
use pumpkin_data::sound::Sound;
use pumpkin_data::sound::SoundCategory;
use pumpkin_data::world::CHAT; use pumpkin_data::world::CHAT;
use pumpkin_inventory::player::PlayerInventory; use pumpkin_inventory::player::PlayerInventory;
use pumpkin_inventory::InventoryError; use pumpkin_inventory::InventoryError;
@ -741,27 +743,40 @@ impl Player {
return; return;
} }
// TODO: set as camera entity when specator
let world = &entity.world; let world = &entity.world;
let player_victim = world.get_player_by_id(entity_id.0).await; 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 let Some(player_victim) = player_victim {
if player_victim.living_entity.health.load() <= 0.0 { if player_victim.living_entity.health.load() <= 0.0 {
// you can trigger this from a non-modded / innocent client client, // you can trigger this from a non-modded / innocent client client,
// so we shouldn't kick the player // so we shouldn't kick the player
return; return;
} }
self.attack(&player_victim).await; if config.protect_creative
} else if let Some(entity_victim) = entity_victim { && player_victim.gamemode.load() == GameMode::Creative
// Checks if victim is a living entity {
if let Some(entity_victim) = entity_victim.get_living_entity() { world
if entity_victim.health.load() <= 0.0 { .play_sound(
return; Sound::EntityPlayerAttackNodamage,
} SoundCategory::Players,
entity_victim.entity.set_pose(EntityPose::Dying).await; &player_victim.position(),
entity_victim.kill().await; )
world.clone().remove_entity(&entity_victim.entity).await; .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 { } else {
log::error!( log::error!(
"Player id {} interacted with entity id {} which was not found.", "Player id {} interacted with entity id {} which was not found.",
@ -775,12 +790,6 @@ impl Player {
.await; .await;
return; 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 => { ActionType::Interact | ActionType::InteractAt => {
log::debug!("todo"); log::debug!("todo");