mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
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:
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user