feat: ender pearl
This commit is contained in:
parent
9b2bd09dcc
commit
d8a124cf92
137
pumpkin/src/entity/projectile/ender_pearl.rs
Normal file
137
pumpkin/src/entity/projectile/ender_pearl.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use crate::{
|
||||
entity::{Entity, EntityBase, EntityBaseFuture, NBTStorage, projectile::ThrownItemEntity},
|
||||
server::Server,
|
||||
};
|
||||
use pumpkin_data::entity::EntityStatus;
|
||||
use pumpkin_data::particle::Particle;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
pub struct EnderPearlEntity {
|
||||
pub thrown: ThrownItemEntity,
|
||||
}
|
||||
|
||||
impl EnderPearlEntity {
|
||||
pub async fn new(entity: Entity) -> Self {
|
||||
entity.set_velocity(Vector3::new(0.0, 0.1, 0.0)).await;
|
||||
|
||||
let thrown = ThrownItemEntity {
|
||||
entity,
|
||||
owner_id: None,
|
||||
collides_with_projectiles: false,
|
||||
has_hit: AtomicBool::new(false),
|
||||
};
|
||||
|
||||
Self { thrown }
|
||||
}
|
||||
|
||||
pub async fn new_shot(entity: Entity, shooter: &Entity) -> Self {
|
||||
let thrown = ThrownItemEntity::new(entity, shooter);
|
||||
thrown
|
||||
.entity
|
||||
.set_velocity(Vector3::new(0.0, 0.1, 0.0))
|
||||
.await;
|
||||
Self { thrown }
|
||||
}
|
||||
}
|
||||
|
||||
impl NBTStorage for EnderPearlEntity {}
|
||||
|
||||
impl EntityBase for EnderPearlEntity {
|
||||
fn tick<'a>(
|
||||
&'a self,
|
||||
caller: Arc<dyn EntityBase>,
|
||||
server: &'a Server,
|
||||
) -> EntityBaseFuture<'a, ()> {
|
||||
Box::pin(async move { self.thrown.process_tick(caller, server).await })
|
||||
}
|
||||
|
||||
fn get_entity(&self) -> &Entity {
|
||||
self.thrown.get_entity()
|
||||
}
|
||||
|
||||
fn get_living_entity(&self) -> Option<&crate::entity::living::LivingEntity> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_nbt_storage(&self) -> &dyn NBTStorage {
|
||||
self
|
||||
}
|
||||
|
||||
fn cast_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn on_hit(&self, hit: crate::entity::projectile::ProjectileHit) -> EntityBaseFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let entity = self.get_entity();
|
||||
let world = entity.world.load();
|
||||
|
||||
// Spawn portal particles at hit position
|
||||
let hit_pos = hit.hit_pos();
|
||||
for _ in 0..32 {
|
||||
let offset = Vector3::new(
|
||||
rand::random::<f32>() as f64 - 0.5,
|
||||
rand::random::<f32>() as f64 * 2.0,
|
||||
rand::random::<f32>() as f64 - 0.5,
|
||||
);
|
||||
let speed = Vector3::new(
|
||||
rand::random::<f64>() - 0.5,
|
||||
0.0,
|
||||
rand::random::<f64>() - 0.5,
|
||||
);
|
||||
|
||||
world
|
||||
.spawn_particle(
|
||||
hit_pos.add(&offset),
|
||||
Vector3::new(speed.x as f32, speed.y as f32, speed.z as f32),
|
||||
1.0,
|
||||
1,
|
||||
Particle::Portal,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if let Some(owner_id) = self.thrown.owner_id
|
||||
&& let Some(owner) = world.get_entity_by_id(owner_id)
|
||||
{
|
||||
// Teleport owner to pearl's position before hit (approx)
|
||||
let teleport_pos = entity.pos.load();
|
||||
|
||||
// In vanilla, teleport handles everything including sound
|
||||
owner
|
||||
.clone()
|
||||
.teleport(
|
||||
teleport_pos,
|
||||
Some(owner.get_entity().yaw.load()),
|
||||
Some(owner.get_entity().pitch.load()),
|
||||
world.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Play teleport sound at new position
|
||||
world
|
||||
.play_sound(
|
||||
Sound::EntityPlayerTeleport,
|
||||
SoundCategory::Players,
|
||||
&teleport_pos,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Deal 5 damage to owner
|
||||
owner
|
||||
.damage(
|
||||
owner.as_ref(),
|
||||
5.0,
|
||||
pumpkin_data::damage::DamageType::ENDER_PEARL,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
world.send_entity_status(entity, EntityStatus::Death).await;
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ use std::{
|
||||
};
|
||||
pub mod arrow;
|
||||
pub mod egg;
|
||||
pub mod ender_pearl;
|
||||
pub mod eye_of_ender;
|
||||
pub mod firework_rocket;
|
||||
pub mod lingering_potion;
|
||||
@ -27,6 +28,7 @@ pub fn is_projectile(entity_type: &EntityType) -> bool {
|
||||
|| *entity_type == EntityType::WIND_CHARGE
|
||||
|| *entity_type == EntityType::SPLASH_POTION
|
||||
|| *entity_type == EntityType::LINGERING_POTION
|
||||
|| *entity_type == EntityType::ENDER_PEARL
|
||||
}
|
||||
|
||||
pub struct ThrownItemEntity {
|
||||
|
||||
@ -7,6 +7,9 @@ use uuid::Uuid;
|
||||
use crate::entity::boss::ender_dragon::EnderDragonEntity;
|
||||
use crate::entity::mob::blaze::BlazeEntity;
|
||||
use crate::entity::mob::zombie::zombie_villager::ZombieVillagerEntity;
|
||||
use crate::entity::projectile::egg::EggEntity;
|
||||
use crate::entity::projectile::ender_pearl::EnderPearlEntity;
|
||||
use crate::entity::projectile::snowball::SnowballEntity;
|
||||
use crate::{
|
||||
entity::{
|
||||
Entity, EntityBase,
|
||||
@ -80,6 +83,9 @@ pub async fn from_type(
|
||||
id if id == EntityType::ARMOR_STAND.id => Arc::new(ArmorStandEntity::new(entity)),
|
||||
id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)),
|
||||
id if id == EntityType::END_CRYSTAL.id => Arc::new(EndCrystalEntity::new(entity)),
|
||||
id if id == EntityType::ENDER_PEARL.id => Arc::new(EnderPearlEntity::new(entity).await),
|
||||
id if id == EntityType::SNOWBALL.id => Arc::new(SnowballEntity::new(entity).await),
|
||||
id if id == EntityType::EGG.id => Arc::new(EggEntity::new(entity).await),
|
||||
id if id == EntityType::SILVERFISH.id => SilverfishEntity::new(entity).await,
|
||||
id if id == EntityType::SLIME.id => SlimeEntity::new(entity),
|
||||
// Fallback Entity
|
||||
|
||||
@ -51,6 +51,19 @@ impl ItemBehaviour for EggItem {
|
||||
egg.thrown
|
||||
.set_velocity_from(&player.living_entity.entity, pitch, yaw, 0.0, POWER, 1.0);
|
||||
world.spawn_entity(Arc::new(egg)).await;
|
||||
|
||||
// Consume item
|
||||
let held_item = player.inventory.held_item();
|
||||
let mut main_hand = held_item.lock().await;
|
||||
if !main_hand.is_empty() && Self::ids().contains(&main_hand.item.id) {
|
||||
main_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
} else {
|
||||
let off_hand_item = player.inventory.off_hand_item().await;
|
||||
let mut off_hand = off_hand_item.lock().await;
|
||||
if !off_hand.is_empty() && Self::ids().contains(&off_hand.item.id) {
|
||||
off_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
71
pumpkin/src/item/items/ender_pearl.rs
Normal file
71
pumpkin/src/item/items/ender_pearl.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::entity::Entity;
|
||||
use crate::entity::player::Player;
|
||||
use crate::entity::projectile::ender_pearl::EnderPearlEntity;
|
||||
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::Sound;
|
||||
|
||||
pub struct EnderPearlItem;
|
||||
|
||||
impl ItemMetadata for EnderPearlItem {
|
||||
fn ids() -> Box<[u16]> {
|
||||
[Item::ENDER_PEARL.id].into()
|
||||
}
|
||||
}
|
||||
|
||||
const POWER: f32 = 1.5;
|
||||
|
||||
impl ItemBehaviour for EnderPearlItem {
|
||||
fn normal_use<'a>(
|
||||
&'a self,
|
||||
_item: &'a Item,
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let position = player.position();
|
||||
let world = player.world();
|
||||
world
|
||||
.play_sound(
|
||||
Sound::EntityEnderPearlThrow,
|
||||
pumpkin_data::sound::SoundCategory::Neutral,
|
||||
&position,
|
||||
)
|
||||
.await;
|
||||
|
||||
let entity = Entity::new(world.clone(), position, &EntityType::ENDER_PEARL);
|
||||
let pearl = EnderPearlEntity::new_shot(entity, &player.living_entity.entity).await;
|
||||
let yaw = player.living_entity.entity.yaw.load();
|
||||
let pitch = player.living_entity.entity.pitch.load();
|
||||
pearl.thrown.set_velocity_from(
|
||||
&player.living_entity.entity,
|
||||
pitch,
|
||||
yaw,
|
||||
0.0,
|
||||
POWER,
|
||||
1.0,
|
||||
);
|
||||
world.spawn_entity(Arc::new(pearl)).await;
|
||||
|
||||
// Consume item
|
||||
let held_item = player.inventory.held_item();
|
||||
let mut main_hand = held_item.lock().await;
|
||||
if !main_hand.is_empty() && main_hand.item.id == Item::ENDER_PEARL.id {
|
||||
main_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
} else {
|
||||
let off_hand_item = player.inventory.off_hand_item().await;
|
||||
let mut off_hand = off_hand_item.lock().await;
|
||||
if !off_hand.is_empty() && off_hand.item.id == Item::ENDER_PEARL.id {
|
||||
off_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ pub mod dye;
|
||||
pub mod egg;
|
||||
pub mod end_crystal;
|
||||
pub mod ender_eye;
|
||||
pub mod ender_pearl;
|
||||
pub mod firework_rocket;
|
||||
pub mod glowing_ink_sac;
|
||||
pub mod hoe;
|
||||
@ -43,6 +44,7 @@ use bucket::{EmptyBucketItem, FilledBucketItem};
|
||||
use dye::DyeItem;
|
||||
use egg::EggItem;
|
||||
use ender_eye::EnderEyeItem;
|
||||
use ender_pearl::EnderPearlItem;
|
||||
use glowing_ink_sac::GlowingInkSacItem;
|
||||
use hoe::HoeItem;
|
||||
use honeycomb::HoneyCombItem;
|
||||
@ -82,6 +84,7 @@ pub fn default_registry() -> Arc<ItemRegistry> {
|
||||
manager.register(HoneyCombItem);
|
||||
manager.register(NameTagItem);
|
||||
manager.register(EnderEyeItem);
|
||||
manager.register(EnderPearlItem);
|
||||
manager.register(FireChargeItem);
|
||||
manager.register(DyeItem);
|
||||
manager.register(FireworkRocketItem);
|
||||
|
||||
@ -48,6 +48,19 @@ impl ItemBehaviour for SnowBallItem {
|
||||
1.0,
|
||||
);
|
||||
world.spawn_entity(Arc::new(snowball)).await;
|
||||
|
||||
// Consume item
|
||||
let held_item = player.inventory.held_item();
|
||||
let mut main_hand = held_item.lock().await;
|
||||
if !main_hand.is_empty() && main_hand.item.id == Item::SNOWBALL.id {
|
||||
main_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
} else {
|
||||
let off_hand_item = player.inventory.off_hand_item().await;
|
||||
let mut off_hand = off_hand_item.lock().await;
|
||||
if !off_hand.is_empty() && off_hand.item.id == Item::SNOWBALL.id {
|
||||
off_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user