Pumpkin/crates/pumpkin/src/entity/projectile/fireball.rs
2026-08-18 18:49:48 +02:00

279 lines
8.5 KiB
Rust

use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use tokio::sync::RwLock;
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
use pumpkin_protocol::java::client::play::Metadata;
use pumpkin_util::math::atomic_f32::AtomicF32;
use pumpkin_util::math::vector3::Vector3;
use crate::{
entity::{
Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture,
projectile::{ProjectileHit, ThrownItemEntity},
projectile_deflection::ProjectileDeflectionType,
},
server::Server,
};
pub const MIN_CAMERA_DISTANCE_SQUARED: f32 = 12.25;
pub const INITIAL_ACCELERATION_POWER: f64 = 0.1;
pub const DEFLECTION_SCALE: f64 = 0.5;
pub const DEFAULT_EXPLOSION_POWER: f32 = 1.0;
pub const AIR_INERTIA: f64 = 0.95;
pub const WATER_INERTIA: f64 = 0.8;
pub struct FireballEntity {
pub thrown: ThrownItemEntity,
pub item_stack: RwLock<ItemStack>,
pub acceleration_power: AtomicU64,
pub explosion_power: AtomicF32,
}
impl FireballEntity {
#[must_use]
pub fn new(entity: Entity) -> Self {
let thrown = ThrownItemEntity {
entity,
owner_id: None,
collides_with_projectiles: false,
has_hit: AtomicBool::new(false),
gravity: 0.0,
};
Self {
thrown,
item_stack: RwLock::new(Self::get_default_item()),
acceleration_power: AtomicU64::new(INITIAL_ACCELERATION_POWER.to_bits()),
explosion_power: AtomicF32::new(DEFAULT_EXPLOSION_POWER),
}
}
#[must_use]
pub fn new_shot(entity: Entity, shooter: &Entity, direction: Vector3<f64>) -> Self {
let thrown = ThrownItemEntity::new(entity, shooter, 0.0);
let accel = INITIAL_ACCELERATION_POWER;
let vel = direction.normalize().multiply(accel, accel, accel);
thrown.entity.velocity.store(vel);
Self {
thrown,
item_stack: RwLock::new(Self::get_default_item()),
acceleration_power: AtomicU64::new(accel.to_bits()),
explosion_power: AtomicF32::new(DEFAULT_EXPLOSION_POWER),
}
}
#[must_use]
pub fn new_directional(
entity: Entity,
direction: Vector3<f64>,
acceleration_power: f64,
) -> Self {
let thrown = ThrownItemEntity {
entity,
owner_id: None,
collides_with_projectiles: false,
has_hit: AtomicBool::new(false),
gravity: 0.0,
};
let vel = direction.normalize().multiply(
acceleration_power,
acceleration_power,
acceleration_power,
);
thrown.entity.velocity.store(vel);
Self {
thrown,
item_stack: RwLock::new(Self::get_default_item()),
acceleration_power: AtomicU64::new(acceleration_power.to_bits()),
explosion_power: AtomicF32::new(DEFAULT_EXPLOSION_POWER),
}
}
#[must_use]
pub fn get_default_item() -> ItemStack {
ItemStack::new(1, &Item::FIRE_CHARGE)
}
pub async fn get_item(&self) -> ItemStack {
self.item_stack.read().await.clone()
}
pub async fn set_item(&self, source: ItemStack) {
let new_item = if source.item_count == 0 {
Self::get_default_item()
} else {
let mut item = source;
item.item_count = 1;
item
};
*self.item_stack.write().await = new_item.clone();
self.get_entity().send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::fireball::ITEM_STACK,
&ItemStackSerializer::from(new_item),
)],
None,
);
}
pub fn get_acceleration_power(&self) -> f64 {
f64::from_bits(self.acceleration_power.load(Ordering::Relaxed))
}
pub fn set_acceleration_power(&self, power: f64) {
self.acceleration_power
.store(power.to_bits(), Ordering::Relaxed);
}
pub fn get_explosion_power(&self) -> f32 {
self.explosion_power.load(Ordering::Relaxed)
}
pub fn set_explosion_power(&self, power: f32) {
self.explosion_power.store(power, Ordering::Relaxed);
}
pub fn on_deflection(&self, _deflection: &ProjectileDeflectionType, by_attack: bool) {
if by_attack {
self.set_acceleration_power(INITIAL_ACCELERATION_POWER);
} else {
let current = self.get_acceleration_power();
self.set_acceleration_power(current * DEFLECTION_SCALE);
}
}
pub fn should_render_at_sqr_distance(&self, distance_sqr: f64) -> bool {
if self.get_entity().age.load(Ordering::Relaxed) < 2
&& distance_sqr < f64::from(MIN_CAMERA_DISTANCE_SQUARED)
{
false
} else {
let bb_size = self
.get_entity()
.bounding_box
.load()
.get_average_side_length()
* 4.0;
let size = if bb_size.is_nan() { 4.0 } else { bb_size } * 64.0;
distance_sqr < size * size
}
}
}
impl NBTStorage for FireballEntity {
fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
nbt.put_double("acceleration_power", self.get_acceleration_power());
nbt.put_float("ExplosionPower", self.get_explosion_power());
})
}
fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
if let Some(accel) = nbt
.get_double("acceleration_power")
.or_else(|| nbt.get_double("power"))
{
self.set_acceleration_power(accel);
}
if let Some(exp) = nbt.get_float("ExplosionPower") {
self.set_explosion_power(exp);
}
})
}
}
impl EntityBase for FireballEntity {
fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
let entity = self.get_entity();
let stack = self.item_stack.read().await;
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::fireball::ITEM_STACK,
&ItemStackSerializer::from(stack.clone()),
)],
None,
);
})
}
fn tick<'a>(
&'a self,
caller: &'a Arc<dyn EntityBase>,
server: &'a Server,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
let entity = self.get_entity();
let mut velocity = entity.velocity.load();
let inertia = if entity.touching_water.load(Ordering::Relaxed) {
WATER_INERTIA
} else {
AIR_INERTIA
};
let accel = self.get_acceleration_power();
let speed = velocity.length();
if speed > 1e-6 {
let norm = velocity.normalize();
velocity = norm
.multiply(accel, accel, accel)
.add(&velocity)
.multiply(inertia, inertia, inertia);
entity.velocity.store(velocity);
}
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: ProjectileHit) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
let world = self.get_entity().world.load();
if let ProjectileHit::Entity { ref entity, .. } = hit {
let entity_clone = entity.clone();
tokio::spawn(async move {
entity_clone.get_entity().set_on_fire_for(5.0);
let _ = entity_clone
.damage(
entity_clone.as_ref(),
6.0,
pumpkin_data::damage::DamageType::FIREBALL,
)
.await;
});
}
let hit_pos = hit.hit_pos();
world.explode(hit_pos, self.get_explosion_power()).await;
})
}
}