Make non living entities not attackable

Adds a cool abstraction to progress damage, Now implementing End Crystals should be very easy
This commit is contained in:
Alexander Medvedev
2025-02-26 22:48:19 +01:00
parent 01e5719464
commit fe400e4970
10 changed files with 152 additions and 72 deletions

View File

@@ -9,6 +9,7 @@ use crate::world::World;
use async_trait::async_trait;
use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::sound::SoundCategory;
use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block;
@@ -34,11 +35,19 @@ impl PumpkinBlock for TNTBlock {
return BlockActionResult::Continue;
}
let world = player.world().await;
world.break_block(server, &location, None, false).await;
world.set_block_state(&location, 0).await;
let entity = server.add_entity(location.to_f64(), EntityType::TNT, &world);
let pos = entity.pos.load();
let tnt = Arc::new(TNTEntity::new(entity, DEFAULT_POWER, DEFAULT_FUSE));
world.spawn_entity(tnt.clone()).await;
tnt.send_meta_packet().await;
world
.play_sound(
pumpkin_data::sound::Sound::EntityTntPrimed,
SoundCategory::Blocks,
&pos,
)
.await;
BlockActionResult::Consume
}
async fn explode(

View File

@@ -1,7 +1,7 @@
use crossbeam::atomic::AtomicCell;
use pumpkin_data::damage::DamageType;
use super::player::Player;
use super::{EntityBase, player::Player};
pub struct HungerManager {
/// The current hunger level.
@@ -50,7 +50,7 @@ impl HungerManager {
} else if level == 0 {
self.tick_timer.fetch_add(1);
if self.tick_timer.load() >= 80 {
player.living_entity.damage(1.0, DamageType::STARVE).await;
player.damage(1.0, DamageType::STARVE).await;
self.tick_timer.store(0);
}
} else {

View File

@@ -1,5 +1,6 @@
use crate::server::Server;
use async_trait::async_trait;
use pumpkin_data::damage::DamageType;
use pumpkin_protocol::{
client::play::{CTakeItemEntity, MetaDataType, Metadata},
codec::slot::Slot,
@@ -53,6 +54,10 @@ impl EntityBase for ItemEntity {
self.entity.remove().await;
}
}
async fn damage(&self, _amount: f32, _damage_type: DamageType) -> bool {
false
}
async fn on_player_collision(&self, player: Arc<Player>) {
if self.pickup_delay.load(std::sync::atomic::Ordering::Relaxed) == 0 {
let mut inv = player.inventory.lock().await;

View File

@@ -1,10 +1,14 @@
use std::{collections::HashMap, sync::atomic::AtomicI32};
use crate::server::Server;
use async_trait::async_trait;
use crossbeam::atomic::AtomicCell;
use pumpkin_config::ADVANCED_CONFIG;
use pumpkin_data::entity::EffectType;
use pumpkin_data::{damage::DamageType, sound::Sound};
use pumpkin_nbt::tag::NbtTag;
use pumpkin_protocol::client::play::CHurtAnimation;
use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::{
client::play::{
CDamageEvent, CEntityStatus, CSetEquipment, EquipmentSlot, MetaDataType, Metadata,
@@ -15,6 +19,7 @@ use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::item::ItemStack;
use tokio::sync::Mutex;
use super::EntityBase;
use super::{Entity, EntityId, NBTStorage, effect::Effect};
/// Represents a living entity within the game world.
@@ -48,17 +53,6 @@ impl LivingEntity {
}
}
pub fn tick(&self) {
if self
.time_until_regen
.load(std::sync::atomic::Ordering::Relaxed)
> 0
{
self.time_until_regen
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
}
}
pub async fn send_equipment_changes(&self, equipment: &[(EquipmentSlot, ItemStack)]) {
let equipment: Vec<(EquipmentSlot, Slot)> = equipment
.iter()
@@ -151,11 +145,6 @@ impl LivingEntity {
effects.get(&effect).cloned()
}
pub async fn damage(&self, amount: f32, damage_type: DamageType) -> bool {
self.damage_with_context(amount, damage_type, None, None, None)
.await
}
/// Returns if the entity was damaged or not
pub fn check_damage(&self, amount: f32) -> bool {
let regen = self
@@ -236,8 +225,54 @@ impl LivingEntity {
.await
.broadcast_packet_all(&CEntityStatus::new(self.entity.entity_id, 3))
.await;
// TODO: wait
self.entity.remove().await;
}
}
#[async_trait]
impl EntityBase for LivingEntity {
async fn tick(&self, _server: &Server) {
if self
.time_until_regen
.load(std::sync::atomic::Ordering::Relaxed)
> 0
{
self.time_until_regen
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
}
}
async fn damage(&self, amount: f32, damage_type: DamageType) -> bool {
let world = self.entity.world.read().await;
if !self.check_damage(amount) {
return false;
}
let config = &ADVANCED_CONFIG.pvp;
if !self
.damage_with_context(amount, damage_type, None, None, None)
.await
{
return false;
}
if config.hurt_animation {
let entity_id = VarInt(self.entity.entity_id);
world
.broadcast_packet_all(&CHurtAnimation::new(entity_id, self.entity.yaw.load()))
.await;
}
true
}
fn get_entity(&self) -> &Entity {
&self.entity
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
Some(self)
}
}
#[async_trait]
impl NBTStorage for LivingEntity {
async fn write_nbt(&self, nbt: &mut pumpkin_nbt::compound::NbtCompound) {

View File

@@ -24,7 +24,8 @@ pub struct MobEntity {
#[async_trait]
impl EntityBase for MobEntity {
async fn tick(&self, _: &Server) {
async fn tick(&self, server: &Server) {
self.living_entity.tick(server).await;
let mut goals = self.goals.lock().await;
for (goal, running) in goals.iter_mut() {
if *running {

View File

@@ -50,8 +50,24 @@ pub type EntityId = i32;
#[async_trait]
pub trait EntityBase: Send + Sync {
/// Gets Called every tick
async fn tick(&self, _server: &Server) {}
/// Called when a player collides with the entity
async fn tick(&self, server: &Server) {
if let Some(living) = self.get_living_entity() {
living.tick(server).await;
} else {
self.get_entity().tick(server).await;
}
}
/// Returns if damage was successful or not
async fn damage(&self, amount: f32, damage_type: DamageType) -> bool {
if let Some(living) = self.get_living_entity() {
living.damage(amount, damage_type).await
} else {
self.get_entity().damage(amount, damage_type).await
}
}
/// Called when a player collides with a entity
async fn on_player_collision(&self, _player: Arc<Player>) {}
fn get_entity(&self) -> &Entity;
fn get_living_entity(&self) -> Option<&LivingEntity>;
@@ -404,6 +420,10 @@ impl Entity {
#[async_trait]
impl EntityBase for Entity {
async fn damage(&self, _amount: f32, _damage_type: DamageType) -> bool {
false
}
async fn tick(&self, _: &Server) {}
fn get_entity(&self) -> &Entity {

View File

@@ -25,10 +25,9 @@ use pumpkin_protocol::{
bytebuf::packet::Packet,
client::play::{
CAcknowledgeBlockChange, CActionBar, CCombatDeath, CDisguisedChatMessage, CEntityStatus,
CGameEvent, CHurtAnimation, CKeepAlive, CParticle, CPlayDisconnect, CPlayerAbilities,
CPlayerInfoUpdate, CPlayerPosition, CRespawn, CSetExperience, CSetHealth, CSubtitle,
CSystemChatMessage, CTitleText, CUnloadChunk, CUpdateMobEffect, GameEvent, MetaDataType,
PlayerAction,
CGameEvent, CKeepAlive, CParticle, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate,
CPlayerPosition, CRespawn, CSetExperience, CSetHealth, CSubtitle, CSystemChatMessage,
CTitleText, CUnloadChunk, CUpdateMobEffect, GameEvent, MetaDataType, PlayerAction,
},
server::play::{
SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay, SClientTickEnd,
@@ -288,7 +287,6 @@ impl Player {
pub async fn attack(&self, victim: Arc<dyn EntityBase>) {
let world = self.world().await;
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;
@@ -336,56 +334,45 @@ impl Player {
let pos = victim_entity.pos.load();
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
.play_sound(Sound::EntityPlayerHurt, SoundCategory::Players, &pos)
.await;
let attack_type = AttackType::new(self, attack_cooldown_progress as f32).await;
player_attack_sound(&pos, &world, attack_type).await;
if matches!(attack_type, AttackType::Critical) {
damage *= 1.5;
}
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 {
AttackType::Knockback => knockback_strength += 1.0,
AttackType::Sweeping => {
combat::spawn_sweep_particle(attacker_entity, &world, &pos).await;
}
_ => {}
};
if config.knockback {
combat::handle_knockback(attacker_entity, &world, victim_entity, knockback_strength)
.await;
}
if config.hurt_animation {
let entity_id = VarInt(victim_entity.entity_id);
if !victim
.damage(damage as f32, DamageType::PLAYER_ATTACK)
.await
{
world
.broadcast_packet_all(&CHurtAnimation::new(entity_id, attacker_entity.yaw.load()))
.play_sound(
Sound::EntityPlayerAttackNodamage,
SoundCategory::Players,
&self.living_entity.entity.pos.load(),
)
.await;
return;
}
if victim.get_living_entity().is_some() {
let mut knockback_strength = 1.0;
player_attack_sound(&pos, &world, attack_type).await;
match attack_type {
AttackType::Knockback => knockback_strength += 1.0,
AttackType::Sweeping => {
combat::spawn_sweep_particle(attacker_entity, &world, &pos).await;
}
_ => {}
};
if config.knockback {
combat::handle_knockback(
attacker_entity,
&world,
victim_entity,
knockback_strength,
)
.await;
}
}
if config.swing {}
@@ -447,7 +434,7 @@ impl Player {
self.cancel_tasks.notified().await;
}
pub async fn tick(&self) {
pub async fn tick(&self, server: &Server) {
if self
.client
.closed
@@ -493,7 +480,7 @@ impl Player {
self.last_attacked_ticks
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
self.living_entity.tick();
self.living_entity.tick(server).await;
self.hunger_manager.tick(self).await;
// timeout/keep alive handling
@@ -1186,6 +1173,18 @@ impl NBTStorage for Player {
#[async_trait]
impl EntityBase for Player {
async fn damage(&self, amount: f32, damage_type: DamageType) -> bool {
self.world()
.await
.play_sound(
Sound::EntityPlayerHurt,
SoundCategory::Players,
&self.living_entity.entity.pos.load(),
)
.await;
self.living_entity.damage(amount, damage_type).await
}
fn get_entity(&self) -> &Entity {
&self.living_entity.entity
}

View File

@@ -1,5 +1,7 @@
use std::f32::{self};
use async_trait::async_trait;
use pumpkin_data::damage::DamageType;
use pumpkin_util::math::vector3::Vector3;
use super::{Entity, EntityBase, living::LivingEntity};
@@ -73,11 +75,16 @@ impl ThrownItemEntity {
}
}
#[async_trait]
impl EntityBase for ThrownItemEntity {
fn get_entity(&self) -> &Entity {
&self.entity
}
async fn damage(&self, _amount: f32, _damage_type: DamageType) -> bool {
false
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
None
}

View File

@@ -1,5 +1,6 @@
use crate::server::Server;
use async_trait::async_trait;
use pumpkin_data::damage::DamageType;
use pumpkin_macros::block_state;
use pumpkin_protocol::{
client::play::{MetaDataType, Metadata},
@@ -62,6 +63,9 @@ impl EntityBase for TNTEntity {
.await;
}
}
async fn damage(&self, _amount: f32, _damage_type: DamageType) -> bool {
false
}
fn get_entity(&self) -> &Entity {
&self.entity

View File

@@ -284,7 +284,7 @@ impl World {
// player ticks
for player in self.players.read().await.values() {
player.tick().await;
player.tick(server).await;
}
let entities_to_tick: Vec<_> = self.entities.read().await.values().cloned().collect();