1use super::{
2 Arc, BLOCK_BEHAVIORS, BlockLootContext, BlockPos, BlockStateExt, BlockStateId, CLevelEvent,
3 CLevelParticles, CSound, ChunkPos, ConnectionProtocol, DVec3, EncodedPacket, Entity,
4 GLOBAL_SOUND_EVENTS, GameEventContext, ItemStack, LevelReader, LootContext, NetworkConnection,
5 ParticleData, Player, REGISTRY, RegistryExt, SectionPos, SoundEventRef, SoundSource,
6 UpdateFlags, World, WorldEntityManager, entity_loot_ref, fluid_state_to_block, level_events,
7 vanilla_blocks, vanilla_game_events,
8};
9
10pub(super) fn sound_is_within_range(
11 sound: SoundEventRef,
12 volume: f32,
13 distance_squared: f64,
14) -> bool {
15 let range = f64::from(sound.range(volume));
16 distance_squared < range * range
17}
18
19impl World {
20 pub fn level_event(&self, event_type: i32, pos: BlockPos, data: i32, exclude: Option<i32>) {
31 let packet = CLevelEvent::new(event_type, pos, data, false);
32 let Ok(encoded) =
33 EncodedPacket::from_bare(packet, self.compression, ConnectionProtocol::Play)
34 else {
35 log::warn!("Failed to encode level event packet");
36 return;
37 };
38
39 self.players.iter_players(|_, player| {
40 if exclude != Some(player.id())
41 && Self::recipient_within_64_blocks(player.position(), pos)
42 {
43 player.connection.send_encoded(encoded.clone());
44 }
45 true
46 });
47 }
48
49 pub(super) fn recipient_within_64_blocks(player_pos: DVec3, event_pos: BlockPos) -> bool {
50 const MAX_DISTANCE_SQ: f64 = 64.0 * 64.0;
51
52 let dx = f64::from(event_pos.x()) - player_pos.x;
53 let dy = f64::from(event_pos.y()) - player_pos.y;
54 let dz = f64::from(event_pos.z()) - player_pos.z;
55 dx * dx + dy * dy + dz * dz < MAX_DISTANCE_SQ
56 }
57
58 pub fn send_particles(
61 &self,
62 particle: ParticleData,
63 position: DVec3,
64 count: i32,
65 spread: DVec3,
66 speed: f64,
67 ) -> i32 {
68 self.send_particles_with_options(particle, false, false, position, count, spread, speed)
69 }
70
71 #[expect(
75 clippy::too_many_arguments,
76 reason = "keeps Vanilla's two particle visibility flags explicit"
77 )]
78 pub fn send_particles_with_options(
79 &self,
80 particle: ParticleData,
81 override_limiter: bool,
82 always_show: bool,
83 position: DVec3,
84 count: i32,
85 spread: DVec3,
86 speed: f64,
87 ) -> i32 {
88 let packet = CLevelParticles {
89 override_limiter,
90 always_show,
91 x: position.x,
92 y: position.y,
93 z: position.z,
94 x_dist: spread.x as f32,
95 y_dist: spread.y as f32,
96 z_dist: spread.z as f32,
97 max_speed: speed as f32,
98 count,
99 particle,
100 };
101 let Ok(encoded) =
102 EncodedPacket::from_bare(packet, self.compression, ConnectionProtocol::Play)
103 else {
104 log::warn!("Failed to encode level particles packet");
105 return 0;
106 };
107 let mut sent = 0;
108 self.players.iter_players(|_, player| {
109 if Self::particle_recipient_in_range(
110 player.block_position(),
111 position,
112 override_limiter,
113 ) {
114 player.connection.send_encoded(encoded.clone());
115 sent += 1;
116 }
117 true
118 });
119 sent
120 }
121
122 #[expect(
125 clippy::too_many_arguments,
126 reason = "mirrors Vanilla ServerLevel.sendParticles"
127 )]
128 pub fn send_particles_to(
129 self: &Arc<Self>,
130 player: &Player,
131 particle: ParticleData,
132 override_limiter: bool,
133 always_show: bool,
134 position: DVec3,
135 count: i32,
136 spread: DVec3,
137 speed: f64,
138 ) -> bool {
139 if !Arc::ptr_eq(self, &player.get_world())
140 || !Self::particle_recipient_in_range(
141 player.block_position(),
142 position,
143 override_limiter,
144 )
145 {
146 return false;
147 }
148
149 let packet = CLevelParticles {
150 override_limiter,
151 always_show,
152 x: position.x,
153 y: position.y,
154 z: position.z,
155 x_dist: spread.x as f32,
156 y_dist: spread.y as f32,
157 z_dist: spread.z as f32,
158 max_speed: speed as f32,
159 count,
160 particle,
161 };
162 let Ok(encoded) =
163 EncodedPacket::from_bare(packet, self.compression, ConnectionProtocol::Play)
164 else {
165 log::warn!("Failed to encode level particles packet");
166 return false;
167 };
168 player.connection.send_encoded(encoded);
169 true
170 }
171
172 pub(super) fn particle_recipient_in_range(
173 player_block_pos: BlockPos,
174 particle_pos: DVec3,
175 override_limiter: bool,
176 ) -> bool {
177 let (x, y, z) = player_block_pos.get_center();
178 let radius = if override_limiter { 512.0 } else { 32.0 };
179 DVec3::new(x, y, z).distance_squared(particle_pos) < radius * radius
180 }
181
182 pub fn global_level_event(&self, event_type: i32, pos: BlockPos, data: i32) {
192 if !self.get_game_rule(&GLOBAL_SOUND_EVENTS) {
193 self.level_event(event_type, pos, data, None);
194 return;
195 }
196
197 let packet = CLevelEvent::new(event_type, pos, data, true);
198 self.players.iter_players(|_, player| {
199 player.send_packet(packet.clone());
200 true
201 });
202 }
203
204 pub fn destroy_block_effect(&self, pos: BlockPos, block_state_id: u32, exclude: Option<i32>) {
213 self.level_event(
214 level_events::PARTICLES_DESTROY_BLOCK,
215 pos,
216 block_state_id as i32,
217 exclude,
218 );
219 }
220
221 pub fn destroy_block(self: &Arc<Self>, pos: BlockPos, drop_items: bool) -> bool {
228 self.destroy_block_with_limit(pos, drop_items, 512)
229 }
230
231 pub fn remove_block(self: &Arc<Self>, pos: BlockPos, moved_by_piston: bool) -> bool {
235 let state = self.get_block_state(pos);
236 let replacement = fluid_state_to_block(state.get_fluid_state());
237 let mut flags = UpdateFlags::UPDATE_ALL;
238 if moved_by_piston {
239 flags |= UpdateFlags::UPDATE_MOVE_BY_PISTON;
240 }
241 self.set_block(pos, replacement, flags)
242 }
243
244 pub fn destroy_block_by_entity(
246 self: &Arc<Self>,
247 pos: BlockPos,
248 drop_items: bool,
249 entity: &dyn Entity,
250 ) -> bool {
251 self.destroy_block_with_limit_and_entity(pos, drop_items, 512, Some(entity))
252 }
253
254 pub fn destroy_block_with_limit(
259 self: &Arc<Self>,
260 pos: BlockPos,
261 drop_items: bool,
262 recursion_left: i32,
263 ) -> bool {
264 self.destroy_block_with_limit_and_entity(pos, drop_items, recursion_left, None)
265 }
266
267 pub(super) fn destroy_block_with_limit_and_entity(
268 self: &Arc<Self>,
269 pos: BlockPos,
270 drop_items: bool,
271 recursion_left: i32,
272 entity: Option<&dyn Entity>,
273 ) -> bool {
274 let state = self.get_block_state(pos);
275 if state.is_air() {
276 return false;
277 }
278
279 let block = state.get_block();
280 let is_fire = block == &vanilla_blocks::FIRE || block == &vanilla_blocks::SOUL_FIRE;
281 if !is_fire {
282 self.destroy_block_effect(pos, u32::from(state.0), None);
283 }
284
285 if drop_items {
286 self.drop_resources_with_entity(state, pos, entity);
287 }
289
290 let replacement = fluid_state_to_block(state.get_fluid_state());
293 let destroyed =
294 self.set_block_with_limit(pos, replacement, UpdateFlags::UPDATE_ALL, recursion_left);
295 if destroyed {
296 self.game_event(
297 &vanilla_game_events::BLOCK_DESTROY,
298 pos,
299 &GameEventContext::new(entity, Some(state)),
300 );
301 }
302 destroyed
303 }
304
305 pub fn drop_resources(self: &Arc<Self>, state: BlockStateId, pos: BlockPos) {
312 self.drop_resources_with_entity(state, pos, None);
313 }
314
315 pub(super) fn drop_resources_with_entity(
316 self: &Arc<Self>,
317 state: BlockStateId,
318 pos: BlockPos,
319 entity: Option<&dyn Entity>,
320 ) {
321 let context = BlockLootContext::new(self, pos).with_entity(entity);
322 for item in context.get_drops(state) {
323 if !item.is_empty() {
324 self.pop_resource(pos, item);
325 }
326 }
327 BLOCK_BEHAVIORS
328 .get_behavior(state.get_block())
329 .spawn_after_break(state, self, pos, &ItemStack::empty(), true);
330 }
331
332 pub(crate) fn block_drops(
333 state: BlockStateId,
334 context: &BlockLootContext<'_>,
335 ) -> Vec<ItemStack> {
336 let behavior = BLOCK_BEHAVIORS.get_behavior(state.get_block());
337 behavior
338 .get_drops(state, context)
339 .unwrap_or_else(|| Self::default_block_drops(state, context))
340 }
341
342 pub(super) fn default_block_drops(
343 state: BlockStateId,
344 context: &BlockLootContext<'_>,
345 ) -> Vec<ItemStack> {
346 let block = state.get_block();
347 let loot_key = steel_utils::Identifier::vanilla(format!("blocks/{}", block.key.path));
348
349 let Some(loot_table) = REGISTRY.loot_tables.by_key(&loot_key) else {
350 return Vec::new();
351 };
352
353 let mut rng = rand::rng();
354 let mut ctx = LootContext::new(&mut rng)
355 .with_luck(context.luck())
356 .with_block_state(state)
357 .with_origin(
358 f64::from(context.pos().x()),
359 f64::from(context.pos().y()),
360 f64::from(context.pos().z()),
361 );
362 if let Some(tool) = context.tool() {
363 ctx = ctx.with_tool(tool);
364 }
365 if let Some(entity) = context.entity() {
366 ctx = ctx.with_this_entity(entity_loot_ref(entity));
367 }
368
369 loot_table.get_random_items(&mut ctx)
370 }
371
372 pub fn play_sound(
386 &self,
387 sound: SoundEventRef,
388 source: SoundSource,
389 pos: BlockPos,
390 volume: f32,
391 pitch: f32,
392 exclude: Option<i32>,
393 ) {
394 self.play_sound_at(
395 sound,
396 source,
397 DVec3::new(
398 f64::from(pos.x()) + 0.5,
399 f64::from(pos.y()) + 0.5,
400 f64::from(pos.z()) + 0.5,
401 ),
402 volume,
403 pitch,
404 exclude,
405 );
406 }
407
408 pub fn play_sound_at(
410 &self,
411 sound: SoundEventRef,
412 source: SoundSource,
413 pos: DVec3,
414 volume: f32,
415 pitch: f32,
416 exclude: Option<i32>,
417 ) {
418 let chunk = ChunkPos::new(
419 SectionPos::block_to_section_coord(pos.x.floor() as i32),
420 SectionPos::block_to_section_coord(pos.z.floor() as i32),
421 );
422
423 let seed = rand::random::<i64>();
425 let packet = CSound::new(sound, source, pos, volume, pitch, seed);
426 let Ok(encoded) =
427 EncodedPacket::from_bare(packet, self.compression, ConnectionProtocol::Play)
428 else {
429 log::warn!("Failed to encode sound packet");
430 return;
431 };
432
433 for entity_id in self.player_area_map.get_tracking_players(chunk) {
435 if exclude == Some(entity_id) {
437 continue;
438 }
439 if let Some(player) = self.players.get_by_entity_id(entity_id) {
440 let player_pos = player.position();
441 let dx = player_pos.x - pos.x;
442 let dy = player_pos.y - pos.y;
443 let dz = player_pos.z - pos.z;
444 let dist_sq = dx * dx + dy * dy + dz * dz;
445
446 if sound_is_within_range(sound, volume, dist_sq) {
447 player.connection.send_encoded(encoded.clone());
448 }
449 }
450 }
451 }
452
453 pub fn play_block_sound(
465 &self,
466 sound: SoundEventRef,
467 pos: BlockPos,
468 volume: f32,
469 pitch: f32,
470 exclude: Option<i32>,
471 ) {
472 self.play_sound(sound, SoundSource::Blocks, pos, volume, pitch, exclude);
473 }
474
475 #[must_use]
477 pub(crate) const fn entity_manager(&self) -> &WorldEntityManager {
478 &self.entity_manager
479 }
480}