steel_core/entity/entities/objects/projectiles/
lingering_potion.rs1use std::sync::{Arc, Weak};
4
5use glam::DVec3;
6use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView;
7use simdnbt::owned::NbtCompound;
8use steel_macros::entity_behavior;
9use steel_protocol::packets::game::SoundSource;
10use steel_registry::entity_type::EntityTypeRef;
11use steel_registry::item_stack::ItemStack;
12use steel_registry::items::ItemRef;
13use steel_registry::vanilla_entity_data::LingeringPotionEntityData;
14use steel_registry::{vanilla_entities, vanilla_items};
15use steel_utils::locks::SyncMutex;
16use steel_utils::{DowncastType, DowncastTypeKey};
17
18use crate::entity::damage::DamageSource;
19use crate::entity::entities::AreaEffectCloudEntity;
20use crate::entity::{
21 AbstractThrownPotion, Entity, EntityBase, EntityBaseLoad, EntitySyncedData, LivingEntity,
22 Projectile, ProjectileBase, ProjectileHit, SharedEntity, ThrowableItemProjectile,
23 ThrowableProjectile, apply_implicit_item_stack_components, next_entity_id,
24};
25use crate::world::{ClipHitResult, World};
26
27const CLOUD_RADIUS: f32 = 3.0;
28const CLOUD_RADIUS_ON_USE: f32 = -0.5;
29const CLOUD_DURATION: i32 = 600;
30const CLOUD_WAIT_TIME: i32 = 10;
31
32#[entity_behavior(class = "ThrownLingeringPotion")]
34pub struct LingeringPotionEntity {
35 base: EntityBase,
37 entity_type: EntityTypeRef,
39 entity_data: SyncMutex<LingeringPotionEntityData>,
41 projectile_base: ProjectileBase,
43}
44
45unsafe impl DowncastType for LingeringPotionEntity {
47 const TYPE_KEY: DowncastTypeKey = DowncastTypeKey::new("steel:entity/lingering_potion");
48}
49
50impl LingeringPotionEntity {
51 #[must_use]
53 pub fn new(entity_type: EntityTypeRef, id: i32, position: DVec3, world: Weak<World>) -> Self {
54 Self {
55 base: EntityBase::new(id, position, entity_type.dimensions, world),
56 entity_type,
57 entity_data: SyncMutex::new(LingeringPotionEntityData::new()),
58 projectile_base: ProjectileBase::new(),
59 }
60 }
61
62 #[must_use]
64 pub fn from_saved(entity_type: EntityTypeRef, load: EntityBaseLoad) -> Self {
65 Self {
66 base: EntityBase::from_load(load, entity_type.dimensions),
67 entity_type,
68 entity_data: SyncMutex::new(LingeringPotionEntityData::new()),
69 projectile_base: ProjectileBase::new(),
70 }
71 }
72}
73
74impl Entity for LingeringPotionEntity {
75 fn base(&self) -> &EntityBase {
76 &self.base
77 }
78
79 fn entity_type(&self) -> EntityTypeRef {
80 self.entity_type
81 }
82
83 fn tick(self: Arc<Self>) {
84 self.throwable_projectile_tick();
85 }
86
87 fn get_default_gravity(&self) -> f64 {
88 self.thrown_potion_default_gravity()
89 }
90
91 fn sound_source(&self) -> SoundSource {
92 SoundSource::Neutral
93 }
94
95 fn spawn_data(&self) -> i32 {
96 self.get_owner().map_or(0, |owner| owner.id())
97 }
98
99 fn restore_owner_reference(&self, owner: &SharedEntity) {
100 self.cache_owner_entity(owner);
101 }
102
103 fn projectile_owner_uuid(&self) -> Option<uuid::Uuid> {
104 self.owner_uuid()
105 }
106
107 fn projectile_owner(&self) -> Option<SharedEntity> {
108 self.get_owner()
109 }
110
111 fn synced_data(&self) -> Option<&dyn EntitySyncedData> {
112 Some(&self.entity_data)
113 }
114
115 fn save_additional(&self, nbt: &mut NbtCompound) {
116 self.save_projectile(nbt);
117 self.save_throwable_item(nbt);
118 }
119
120 fn load_additional(&self, nbt: BorrowedNbtCompoundView<'_, '_>) {
121 self.load_projectile(nbt);
122 self.load_throwable_item(nbt);
123 }
124}
125
126impl Projectile for LingeringPotionEntity {
127 fn projectile_base(&self) -> &ProjectileBase {
128 &self.projectile_base
129 }
130
131 fn calculate_horizontal_hurt_knockback_direction(
132 &self,
133 hurt_entity: &dyn LivingEntity,
134 _damage_source: &DamageSource,
135 ) -> (f64, f64) {
136 self.thrown_potion_knockback_direction(hurt_entity)
137 }
138
139 fn on_hit(self: Arc<Self>, hit: &ProjectileHit) {
140 self.thrown_potion_on_hit(hit);
141 }
142
143 fn on_hit_block(self: Arc<Self>, hit: &ClipHitResult) {
144 self.thrown_potion_on_hit_block(hit);
145 }
146}
147
148impl ThrowableProjectile for LingeringPotionEntity {}
149
150impl ThrowableItemProjectile for LingeringPotionEntity {
151 fn get_default_item(&self) -> ItemRef {
152 &vanilla_items::LINGERING_POTION
153 }
154
155 fn set_item(&self, item: ItemStack) {
156 self.entity_data
157 .lock()
158 .throwable_item_projectile
159 .item_stack
160 .set(item);
161 }
162
163 fn get_item(&self) -> ItemStack {
164 self.entity_data
165 .lock()
166 .throwable_item_projectile
167 .item_stack
168 .get()
169 .clone()
170 }
171}
172
173impl AbstractThrownPotion for LingeringPotionEntity {
174 fn on_hit_as_potion(
175 self: &Arc<Self>,
176 world: &Arc<World>,
177 potion_item: &ItemStack,
178 hit: &ProjectileHit,
179 ) {
180 let position = match hit {
181 ProjectileHit::Entity(entity_hit) => entity_hit.entity.position(),
182 ProjectileHit::Block { .. } => self.position(),
183 };
184 let cloud = Arc::new(AreaEffectCloudEntity::new(
185 &vanilla_entities::AREA_EFFECT_CLOUD,
186 next_entity_id(),
187 position,
188 Arc::downgrade(world),
189 ));
190 cloud.set_owner(self.get_owner().as_ref());
191 cloud.set_radius(CLOUD_RADIUS);
192 cloud.set_radius_on_use(CLOUD_RADIUS_ON_USE);
193 cloud.set_duration(CLOUD_DURATION);
194 cloud.set_wait_time(CLOUD_WAIT_TIME);
195 cloud.set_radius_per_tick(-cloud.radius() / cloud.duration() as f32);
196
197 let cloud: SharedEntity = cloud;
198 apply_implicit_item_stack_components(&cloud, potion_item);
199 if let Err(error) = world.try_add_entity(cloud) {
200 log::debug!("world rejected a lingering potion cloud: {error}");
201 }
202 }
203}