steel_core/entity/
potion_contents.rs1use steel_registry::MobEffectInstance as RegistryMobEffectInstance;
6use steel_registry::data_components::PotionContents;
7
8use crate::behavior::MOB_EFFECT_BEHAVIORS;
9use crate::entity::{LivingEntityRef, MobEffectInstance as RuntimeMobEffectInstance};
10use crate::world::World;
11
12pub(crate) fn apply_potion_contents(
13 contents: &PotionContents,
14 world: &World,
15 user: LivingEntityRef<'_>,
16 duration_scale: f32,
17) {
18 let damage_source_entity = user.living().as_player().map(|_| user.entity());
21 for effect in contents.all_effects() {
22 let behavior = MOB_EFFECT_BEHAVIORS.get_behavior(effect.effect());
23 if let Some(instantaneous) = behavior.as_instantaneous() {
24 instantaneous.apply_instantaneous(
28 world,
29 user.living(),
30 effect.amplifier(),
31 damage_source_entity,
32 damage_source_entity,
33 1.0,
34 );
35 continue;
36 }
37
38 let scaled_effect = effect.with_scaled_duration(duration_scale);
39 user.living().add_mob_effect(to_runtime_instance(
40 &scaled_effect,
41 scaled_effect.duration(),
42 ));
43 }
44}
45
46pub(crate) const fn to_runtime_instance(
49 effect: &RegistryMobEffectInstance,
50 duration: i32,
51) -> RuntimeMobEffectInstance {
52 RuntimeMobEffectInstance::with_duration(effect.effect(), duration, effect.amplifier())
53 .with_ambient(effect.ambient())
54 .with_visible(effect.show_particles())
55 .with_show_icon(effect.show_icon())
56}
57
58pub(crate) const fn to_runtime_instance_icon_from_visibility(
60 effect: &RegistryMobEffectInstance,
61 duration: i32,
62) -> RuntimeMobEffectInstance {
63 to_runtime_instance(effect, duration).with_show_icon(effect.show_particles())
64}
65
66#[cfg(test)]
67mod tests {
68 use std::sync::Arc;
69
70 use steel_registry::data_components::PotionContents;
71 use steel_registry::{
72 MobEffectInstance as RegistryMobEffectInstance, init_vanilla_registry, vanilla_mob_effects,
73 };
74 use steel_utils::ChunkPos;
75
76 use super::{
77 apply_potion_contents, to_runtime_instance, to_runtime_instance_icon_from_visibility,
78 };
79 use crate::behavior::init_behaviors;
80 use crate::entity::{LivingEntity, LivingEntityRef, SharedEntity};
81 use crate::player::Player;
82 use crate::test_support::{TestPlayerBuilder, fresh_test_world, insert_ready_full_chunk};
83
84 #[test]
85 fn splash_rebuild_takes_its_icon_flag_from_visibility() {
86 init_vanilla_registry();
87 let effect = RegistryMobEffectInstance::new(
88 vanilla_mob_effects::LUCK,
89 100,
90 0,
91 false,
92 true,
93 false,
94 None,
95 );
96
97 let drunk = to_runtime_instance(&effect, 100);
98 assert!(drunk.is_visible());
99 assert!(
100 !drunk.show_icon(),
101 "drinking preserves the source icon flag"
102 );
103
104 let splashed = to_runtime_instance_icon_from_visibility(&effect, 100);
105 assert!(splashed.is_visible());
106 assert!(
107 splashed.show_icon(),
108 "the splash rebuild must take show_icon from visible"
109 );
110
111 assert_eq!(splashed.effect(), drunk.effect());
113 assert_eq!(splashed.duration(), drunk.duration());
114 assert_eq!(splashed.amplifier(), drunk.amplifier());
115 assert_eq!(splashed.is_ambient(), drunk.is_ambient());
116 }
117
118 #[test]
123 fn instant_health_amplifier_at_shift_width_does_not_panic_and_wraps_like_vanilla() {
124 init_vanilla_registry();
125 init_behaviors();
126 let world_fixture = fresh_test_world("instant_health_high_amplifier");
127 let world = &world_fixture.world;
128 insert_ready_full_chunk(world, ChunkPos::new(0, 0));
129 let player = TestPlayerBuilder::new(Arc::clone(world), "Test", 1).build();
130 let player_entity: SharedEntity = Arc::<Player>::clone(&player);
131 player.set_health(1.0);
132
133 let contents = PotionContents::new(
134 None,
135 None,
136 vec![RegistryMobEffectInstance::simple(
137 vanilla_mob_effects::INSTANT_HEALTH,
138 1,
139 32,
140 )],
141 None,
142 );
143
144 apply_potion_contents(
145 &contents,
146 world,
147 LivingEntityRef::new(&player_entity).expect("player is living"),
148 1.0,
149 );
150
151 assert_eq!(player.get_health(), 5.0);
153 }
154}