steel_core/world/explosion/server/
entity_effects.rs1use glam::DVec3;
4use steel_registry::vanilla_entity_type_tags::EntityTypeTag;
5use steel_registry::{REGISTRY, TaggedRegistryExt as _, vanilla_attributes};
6use steel_utils::types::GameType;
7use steel_utils::{BlockPos, WorldAabb};
8
9use crate::behavior::BlockCollisionContext;
10use crate::entity::Entity;
11use crate::entity::entities::PrimedTntEntity;
12use crate::world::explosion::ExplosionDamageCalculator as _;
13use crate::world::raycast::ExplosionExposureRaycast;
14
15use super::ServerExplosion;
16use super::exposure::EntityExplosionExposure;
17
18const MIN_DAMAGE_RADIUS: f32 = 1.0e-5;
19const DAMAGE_RADIUS_SCALE: f32 = 2.0;
20const ENTITY_QUERY_PADDING: f64 = 1.0;
21const NORMALIZE_EPSILON: f64 = 1.0e-5_f32 as f64;
22
23impl ServerExplosion<'_> {
24 pub(super) fn hurt_entities(&mut self) {
25 if self.radius < MIN_DAMAGE_RADIUS {
26 return;
27 }
28
29 let double_radius = f64::from(self.radius * DAMAGE_RADIUS_SCALE);
30 let bounds = WorldAabb::from_min_max(
31 DVec3::new(
32 (self.center.x - double_radius - ENTITY_QUERY_PADDING).floor(),
33 (self.center.y - double_radius - ENTITY_QUERY_PADDING).floor(),
34 (self.center.z - double_radius - ENTITY_QUERY_PADDING).floor(),
35 ),
36 DVec3::new(
37 (self.center.x + double_radius + ENTITY_QUERY_PADDING).floor(),
38 (self.center.y + double_radius + ENTITY_QUERY_PADDING).floor(),
39 (self.center.z + double_radius + ENTITY_QUERY_PADDING).floor(),
40 ),
41 );
42 let source_id = self.source.as_ref().map(|source| source.id());
43 let entities = self.world.get_entities_in_aabb_matching(&bounds, |entity| {
44 source_id != Some(entity.id()) && !entity.is_spectator()
45 });
46 let builtin_entity_effects = self.damage_calculator.has_builtin_entity_effects();
47 let mut exposure_raycast =
48 ExplosionExposureRaycast::new(self.world.as_ref(), BlockCollisionContext::empty());
49 exposure_raycast.configure_clear_grid(
50 BlockPos::from(bounds.min_corner()),
51 BlockPos::from(bounds.max_corner()),
52 );
53 let mut reusable_from_previous_tnt = true;
55
56 for entity in entities {
57 let inert_primed_tnt = builtin_entity_effects
61 && steel_utils::Downcast::downcast_ref::<PrimedTntEntity>(entity.as_ref())
62 .is_some();
63 if !reusable_from_previous_tnt || !inert_primed_tnt {
64 exposure_raycast.clear();
65 }
66 reusable_from_previous_tnt = inert_primed_tnt;
67
68 if entity.ignore_explosion(self) {
69 continue;
70 }
71 let distance = entity.position().distance(self.center) / double_radius;
72 if distance > 1.0 {
73 continue;
74 }
75
76 let delta = entity.explosion_damage_origin() - self.center;
77 let delta_length = delta.length();
78 let direction = if delta_length < NORMALIZE_EPSILON {
79 DVec3::ZERO
80 } else {
81 delta / delta_length
82 };
83 let should_damage = self
84 .damage_calculator
85 .should_damage_entity(self, entity.as_ref());
86 let knockback_multiplier = self.damage_calculator.knockback_multiplier(entity.as_ref());
87 let exposure = if !should_damage && knockback_multiplier == 0.0 {
88 0.0
89 } else {
90 EntityExplosionExposure::capture(entity.as_ref())
91 .calculate_cached_with(&mut exposure_raycast, self.center)
92 };
93
94 if should_damage {
95 let amount =
96 self.damage_calculator
97 .entity_damage_amount(self, entity.as_ref(), exposure);
98 entity.hurt(self.world, &self.damage_source, amount);
99 }
100
101 let knockback_resistance = entity.as_living_entity().map_or(0.0, |living| {
102 living
103 .attributes()
104 .lock()
105 .required_value(vanilla_attributes::EXPLOSION_KNOCKBACK_RESISTANCE)
106 });
107 let knockback_power = (1.0 - distance)
108 * f64::from(exposure)
109 * f64::from(knockback_multiplier)
110 * (1.0 - knockback_resistance);
111 let knockback = direction * knockback_power;
112 entity.push_impulse(knockback);
113
114 if REGISTRY.entity_types.is_in_tag(
115 entity.entity_type(),
116 &EntityTypeTag::REDIRECTABLE_PROJECTILE,
117 ) {
118 if let Some(projectile) = entity.as_projectile() {
119 projectile.set_owner_entity(self.damage_source.causing_entity());
120 }
121 } else if let Some(player) = entity.as_player()
122 && !player.is_spectator()
123 && (player.game_mode() != GameType::Creative || !player.abilities.lock().flying)
124 {
125 self.hit_players.insert(player.id(), knockback);
126 }
127
128 entity.on_explosion_hit(self.source.as_deref());
129 }
130 }
131}