steel_core/world/explosion/
server.rs1use std::sync::Arc;
4
5use glam::DVec3;
6use rustc_hash::FxHashMap;
7use steel_registry::vanilla_game_rules::MOB_GRIEFING;
8use steel_registry::{vanilla_damage_types, vanilla_entities, vanilla_game_events};
9use steel_utils::random::{Random as _, legacy_random::LegacyRandom};
10
11use crate::entity::damage::DamageSource;
12use crate::entity::{Entity, SharedEntity};
13use crate::world::World;
14use crate::world::game_event::GameEventContext;
15
16use super::{
17 BlockInteraction, Explosion, ExplosionDamageCalculator, ImmutableExplosionBlockCalculator,
18 SelectedDamageCalculator,
19};
20
21mod block_effects;
22mod block_rays;
23mod entity_effects;
24mod exposure;
25
26const SMALL_EXPLOSION_RADIUS: f32 = 2.0;
27
28pub(super) struct ServerExplosion<'a> {
29 world: &'a Arc<World>,
30 fire: bool,
31 block_interaction: BlockInteraction,
32 center: DVec3,
33 source: Option<SharedEntity>,
34 indirect_source: Option<SharedEntity>,
35 radius: f32,
36 damage_source: DamageSource,
37 damage_calculator: SelectedDamageCalculator<'a>,
38 immutable_block_calculator: Option<&'a dyn ImmutableExplosionBlockCalculator>,
39 pub(super) hit_players: FxHashMap<i32, DVec3>,
40}
41
42impl<'a> ServerExplosion<'a> {
43 #[expect(
44 clippy::too_many_arguments,
45 reason = "mirrors the Vanilla ServerExplosion construction boundary"
46 )]
47 pub(super) fn new(
48 world: &'a Arc<World>,
49 source: Option<SharedEntity>,
50 damage_source: Option<DamageSource>,
51 damage_calculator: Option<&'a dyn ExplosionDamageCalculator>,
52 immutable_block_calculator: Option<&'a dyn ImmutableExplosionBlockCalculator>,
53 center: DVec3,
54 radius: f32,
55 fire: bool,
56 block_interaction: BlockInteraction,
57 ) -> Self {
58 let indirect_source = source.as_ref().and_then(|source| {
59 if source.as_living_entity().is_some() {
60 Some(Arc::clone(source))
61 } else {
62 source.explosion_indirect_source()
63 }
64 });
65 let damage_source = damage_source
66 .unwrap_or_else(|| default_damage_source(source.clone(), indirect_source.clone()));
67 let damage_calculator = match (damage_calculator, &source) {
68 (Some(calculator), _) => SelectedDamageCalculator::Custom(calculator),
69 (None, Some(source)) => SelectedDamageCalculator::Entity(Arc::clone(source)),
70 (None, None) => SelectedDamageCalculator::Default,
71 };
72 Self {
73 world,
74 fire,
75 block_interaction,
76 center,
77 source,
78 indirect_source,
79 radius,
80 damage_source,
81 damage_calculator,
82 immutable_block_calculator,
83 hit_players: FxHashMap::default(),
84 }
85 }
86
87 pub(super) fn explode(&mut self) -> usize {
88 self.world.game_event_at(
89 &vanilla_game_events::EXPLODE,
90 self.center,
91 &GameEventContext::new(self.source.as_deref(), None),
92 );
93 let mut random = LegacyRandom::from_seed(rand::random());
95 let mut affected = self.calculate_exploded_positions(|| random.next_f32());
96 self.hurt_entities();
97 if self.interacts_with_blocks() {
98 self.interact_with_blocks(&mut affected, &mut random);
99 }
100 if self.fire {
101 self.create_fire(&affected, &mut random);
102 }
103 affected.len()
104 }
105
106 fn interacts_with_blocks(&self) -> bool {
107 self.block_interaction != BlockInteraction::Keep
108 }
109
110 pub(super) fn is_small(&self) -> bool {
111 self.radius < SMALL_EXPLOSION_RADIUS || !self.interacts_with_blocks()
112 }
113}
114
115impl Explosion for ServerExplosion<'_> {
116 fn world(&self) -> &Arc<World> {
117 self.world
118 }
119
120 fn damage_source(&self) -> &DamageSource {
121 &self.damage_source
122 }
123
124 fn block_interaction(&self) -> BlockInteraction {
125 self.block_interaction
126 }
127
128 fn indirect_source_entity(&self) -> Option<&dyn Entity> {
129 self.indirect_source.as_deref()
130 }
131
132 fn direct_source_entity(&self) -> Option<&dyn Entity> {
133 self.source.as_deref()
134 }
135
136 fn radius(&self) -> f32 {
137 self.radius
138 }
139
140 fn center(&self) -> DVec3 {
141 self.center
142 }
143
144 fn should_affect_blocklike_entities(&self) -> bool {
145 let is_wind_charge = self.source.as_ref().is_some_and(|source| {
146 source.entity_type() == &vanilla_entities::BREEZE_WIND_CHARGE
147 || source.entity_type() == &vanilla_entities::WIND_CHARGE
148 });
149 !is_wind_charge
150 && (self.world.get_game_rule(&MOB_GRIEFING)
151 || self.block_interaction.should_affect_blocklike_entities())
152 }
153}
154
155fn default_damage_source(
157 direct: Option<SharedEntity>,
158 indirect: Option<SharedEntity>,
159) -> DamageSource {
160 let damage_type = if direct.is_some() && indirect.is_some() {
161 &vanilla_damage_types::PLAYER_EXPLOSION
162 } else {
163 &vanilla_damage_types::EXPLOSION
164 };
165 let mut damage_source = DamageSource::environment(damage_type);
166 if let Some(direct) = direct {
167 damage_source = damage_source.with_direct_entity(direct);
168 }
169 if let Some(indirect) = indirect {
170 damage_source = damage_source.with_causing_entity(indirect);
171 }
172 damage_source
173}
174
175#[cfg(test)]
176mod tests;