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 {
death_message_type: Option<DeathMessageType>,
exhaustion: f32,
effects: Option<DamageEffects>,
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<DeathMessageType>,
pub exhaustion: f32,
pub effects: Option<DamageEffects>,
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,

View File

@ -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(),

View File

@ -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<f64>) {

View File

@ -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;
}
}

View File

@ -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<Self>) {
pub async fn attack(&self, victim: Arc<dyn EntityBase>) {
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<Self>, server: &Arc<Server>) {
let mut packets = self.client.client_packets_queue.lock().await;

View File

@ -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");