1use std::{
4 io, mem,
5 path::Path,
6 sync::{
7 Arc, LazyLock, Weak,
8 atomic::{AtomicBool, Ordering},
9 },
10 time::Duration,
11};
12
13use crate::chunk::chunk_ticket_manager::{PersistentChunkTickets, TimedChunkTickets};
14use crate::chunk::full_chunk::{FullChunkBlockSetResult, FullChunkRef};
15use crate::chunk::gameplay_chunk_lookup_cache::GameplayChunkLookupCacheScope;
16use crate::chunk::light::{
17 LightLayer, LightSectionEmptinessChange, MAX_LIGHT_LEVEL, has_different_light_properties,
18};
19use crate::chunk::status::ChunkStatus;
20use crate::poi::OccupationStatus;
21use crate::portal::WorldChangeRequest;
22use crate::world::game_event::{
23 GameEventContext, GameEventDispatcher, GameEventListenerCount, GameEventListenerStorage,
24 SharedGameEventListener,
25};
26use crate::{chunk::chunk_map::ChunkMapGameTickTimings, world::weather::Weather};
27use steel_utils::saved_data::{SavedDataManager, names as saved_data_names};
28
29use glam::DVec3;
30use sha2::{Digest, Sha256};
31use steel_protocol::packets::game::{
32 CBlockDestruction, CChangeDifficulty, CGameEvent, CInitializeBorder, CLevelEvent,
33 CLevelParticles, CPlayerChat, CSetBorderCenter, CSetBorderLerpSize, CSetBorderSize,
34 CSetBorderWarningDelay, CSetBorderWarningDistance, CSetEntityData, CSetEntityLink,
35 CSetEquipment, CSound, CSystemChat, CUpdateAttributes, GameEventType, SoundSource,
36};
37use steel_protocol::utils::ConnectionProtocol;
38use steel_protocol::{
39 packet_traits::{ClientPacket, CompressionInfo, EncodedPacket},
40 packets::game::CSetTime,
41};
42
43use rustc_hash::FxHashSet;
44use simdnbt::owned::NbtCompound;
45use steel_registry::biome::{BiomeRef, TemperatureModifier};
46use steel_registry::blocks::block_state_ext::BlockStateExt;
47use steel_registry::blocks::properties::{Axis, BlockStateProperties, Direction};
48use steel_registry::blocks::shapes::{
49 BooleanOp, OffsetVoxelShape, SupportType, VoxelShape, is_offset_face_full, is_shape_full_block,
50 join_is_not_empty,
51};
52use steel_registry::fluid::{FluidRef, FluidState};
53use steel_registry::game_events::GameEventRef;
54use steel_registry::game_rules::{ErasedGameRuleRef, GameRule, GameRuleValue, GameRuleValueType};
55use steel_registry::item_stack::ItemStack;
56use steel_registry::level_events;
57use steel_registry::loot_table::LootContext;
58use steel_registry::particle_type::ParticleData;
59use steel_registry::sound_event::SoundEventRef;
60use steel_registry::vanilla_block_tags::BlockTag;
61use steel_registry::vanilla_game_rules::{
62 BLOCK_DROPS, GLOBAL_SOUND_EVENTS, PLAYERS_NETHER_PORTAL_DEFAULT_DELAY, RANDOM_TICK_SPEED,
63};
64use steel_registry::{REGISTRY, RegistryEntry, RegistryExt, dimension_type::DimensionTypeRef};
65use steel_registry::{block_entity_type::BlockEntityTypeRef, vanilla_dimension_types};
66use steel_registry::{
67 blocks::BlockRef, vanilla_game_rules::ADVANCE_TIME, vanilla_game_rules::ADVANCE_WEATHER,
68};
69use steel_registry::{vanilla_blocks, vanilla_entities, vanilla_game_events, vanilla_poi_types};
70use steel_utils::block_util::FoundRectangle;
71use steel_utils::{
72 Downcast as _,
73 locks::{SyncMutex, SyncRwLock},
74 random::{Random as _, RandomSource, legacy_random::LegacyRandom},
75};
76use steel_worldgen::{biomes::obfuscate_biome_seed, noise::PerlinSimplexNoise};
77
78use steel_utils::{
79 BlockLocalAabb, BlockPos, BlockStateId, ChunkPos, Identifier, PackedBlockPos, SectionPos,
80 WorldAabb,
81 types::{Difficulty, GameType, UpdateFlags},
82};
83use tokio::{runtime::Runtime, time::Instant};
84
85use crate::{
86 ChunkMap,
87 behavior::{BLOCK_BEHAVIORS, BlockCollisionContext, BlockLootContext, FLUID_BEHAVIORS},
88 block_entity::{BlockEntity, SharedBlockEntity, entities::EndGatewayBlockEntity},
89 chunk::{heightmap::HeightmapType, player_chunk_view::PlayerChunkView},
90 chunk_saver::{ChunkStorage, RamOnlyStorage, RegionManager},
91 entity::{
92 AddEntityError, Entity, EntityChangeSenders, EntityChunkCallback, EntityLifecycleChanges,
93 EntityMovementSyncPacket, EntityOwnership, EntityTracker, EntityVisibility,
94 InactiveEntityCallback, MobEffectSyncPacket, RemovalReason, SharedEntity,
95 WorldEntityManager,
96 entities::{ExperienceOrbEntity, ItemEntity},
97 entity_loot_ref,
98 },
99 fluid::{FluidStateExt as _, fluid_state_to_block},
100 level_data::{LevelDataManager, RespawnData, WorldGenerationSettings},
101 player::{LastSeen, Player, connection::NetworkConnection},
102 poi::PointOfInterestStorage,
103};
104
105mod block_entity_ticker;
106mod block_event;
107mod block_region;
108mod block_updates;
109mod border;
110mod broadcasts;
111pub(crate) mod clock;
112mod entity_management;
113mod environment;
114mod events;
115pub mod game_event;
117mod level_effects;
118mod level_reader;
119mod player_index;
120pub(crate) mod player_spawn_finder;
121mod portals;
122mod properties;
123mod raycast;
124mod redstone;
125mod signal_getter;
126mod spawn;
127pub mod tick_scheduler;
128mod weather;
129mod world_entities;
130
131#[cfg(test)]
132mod tests;
133
134pub use crate::config::WorldStorageConfig;
135use crate::worldgen::generators::vanilla::fuzzed_biome_at_block;
136use crate::worldgen::{ChunkGenerator, ChunkGeneratorType};
137use block_event::BlockEventQueue;
138pub(crate) use block_region::{BlockRegionBounds, MAX_BLOCK_REGION_WORKSET_SLOTS};
139use block_updates::CollectingNeighborUpdater;
140pub use border::WorldBorderError;
141use border::{WorldBorder, WorldBorderSnapshot};
142use entity_management::NavigatingMobTracker;
143#[cfg(test)]
144use entity_management::nearest_player_distance_in_range;
145pub use level_reader::{LevelAccessor, LevelReader, ScheduledTickAccess};
146pub use player_index::{PlayerAreaMap, PlayerMap};
147pub use raycast::{ClipBlockShape, ClipFluid, ClipHitResult, RaytraceAction};
148pub use signal_getter::{SignalGetter, SignalQueryContext};
149pub(crate) use signal_getter::{
150 get_best_neighbor_signal, get_control_input_signal, get_signal, is_redstone_conductor,
151};
152pub use tick_scheduler::ScheduledTick;
153
154#[cfg(test)]
155use level_effects::sound_is_within_range;
156#[cfg(test)]
157use portals::{
158 closest_portal_candidate, nether_portal_creation_scan_origin, nether_portal_frame_offset_pos,
159};
160
161const fn initialize_border_packet(snapshot: WorldBorderSnapshot) -> CInitializeBorder {
162 CInitializeBorder {
163 new_center_x: snapshot.center_x,
164 new_center_z: snapshot.center_z,
165 old_size: snapshot.old_size,
166 new_size: snapshot.new_size,
167 lerp_time: snapshot.lerp_time,
168 new_absolute_max_size: snapshot.absolute_max_size,
169 warning_blocks: snapshot.warning_blocks,
170 warning_time: snapshot.warning_time,
171 }
172}
173
174#[derive(Debug)]
176pub struct WorldGameTickTimings {
177 pub elapsed: Duration,
179 pub chunk_map: ChunkMapGameTickTimings,
181 pub entity_tick: Duration,
183}
184
185#[must_use]
187#[derive(Clone, Copy, Debug, Eq, PartialEq)]
188pub enum ConditionalBlockSetResult {
189 Changed,
191 Unchanged,
193 Stale(BlockStateId),
195 Unavailable,
197}
198
199#[derive(Clone)]
201pub struct WorldConfig {
202 pub storage: WorldStorageConfig,
204 pub level_data_path: Option<String>,
206 pub generator: Arc<ChunkGeneratorType>,
208 pub generation_settings: WorldGenerationSettings,
210 pub view_distance: u8,
212 pub simulation_distance: u8,
214 pub max_chained_neighbor_updates: i32,
216 pub compression: Option<CompressionInfo>,
218 pub is_flat: bool,
220 pub sea_level: i32,
222 pub default_gamemode: GameType,
224 pub difficulty: Difficulty,
226}
227
228pub struct World {
230 pub chunk_map: Arc<ChunkMap>,
232 pub players: PlayerMap,
234 pub player_area_map: PlayerAreaMap,
236 pub key: Identifier,
238 pub dimension_type: DimensionTypeRef,
244 pub level_data: SyncRwLock<LevelDataManager>,
246 pub(crate) saved_data: SavedDataManager,
248 world_border: SyncMutex<WorldBorder>,
250 pub view_distance: u8,
252 pub simulation_distance: u8,
254 pub compression: Option<CompressionInfo>,
256 pub is_flat: bool,
258 pub sea_level: i32,
260 pub default_gamemode: GameType,
262 tick_runs_normally: AtomicBool,
265 handling_tick: AtomicBool,
267 block_events: SyncMutex<BlockEventQueue>,
269 neighbor_updater: CollectingNeighborUpdater,
271 entity_manager: WorldEntityManager,
273 block_entity_tickers: block_entity_ticker::WorldBlockEntityTickers,
275 game_event_listener_count: Arc<GameEventListenerCount>,
277 entity_tracker: EntityTracker,
279 navigating_mobs: NavigatingMobTracker,
281 pub weather: SyncMutex<Weather>,
283 redstone_torch_toggles: SyncMutex<redstone::RedstoneTorchToggleTracker>,
285 scheduled_ticks: tick_scheduler::WorldTickScheduler,
287 scheduled_block_ticks_this_tick:
289 SyncMutex<Option<Arc<tick_scheduler::ScheduledTickRunBatch<BlockRef>>>>,
290 scheduled_fluid_ticks_this_tick:
292 SyncMutex<Option<Arc<tick_scheduler::ScheduledTickRunBatch<FluidRef>>>>,
293 pub poi_storage: SyncMutex<PointOfInterestStorage>,
295 pending_world_changes: SyncMutex<Vec<(SharedEntity, WorldChangeRequest)>>,
297}
298
299impl World {
300 pub async fn new_with_config(
312 chunk_runtime: Arc<Runtime>,
313 key: Identifier,
314 dimension_type: DimensionTypeRef,
315 seed: i64,
316 config: WorldConfig,
317 generation_pool: Arc<rayon::ThreadPool>,
318 ) -> io::Result<Arc<Self>> {
319 let chunk_encoding_pool = Arc::clone(&generation_pool);
320 Self::new_with_config_and_encoding_pool(
321 chunk_runtime,
322 key,
323 dimension_type,
324 seed,
325 config,
326 generation_pool,
327 chunk_encoding_pool,
328 )
329 .await
330 }
331
332 pub(crate) async fn new_with_config_and_encoding_pool(
333 chunk_runtime: Arc<Runtime>,
334 key: Identifier,
335 dimension_type: DimensionTypeRef,
336 seed: i64,
337 config: WorldConfig,
338 generation_pool: Arc<rayon::ThreadPool>,
339 chunk_encoding_pool: Arc<rayon::ThreadPool>,
340 ) -> io::Result<Arc<Self>> {
341 let view_distance = config.view_distance;
342 let simulation_distance = config.simulation_distance;
343 let max_chained_neighbor_updates = config.max_chained_neighbor_updates;
344 let compression = config.compression;
345 let is_flat = config.is_flat;
346 let sea_level = config.sea_level;
347 let default_gamemode = config.default_gamemode;
348 let storage: Arc<ChunkStorage> = match &config.storage {
350 WorldStorageConfig::Disk { path } => {
351 Arc::new(ChunkStorage::Disk(RegionManager::new(path.clone())))
352 }
353 WorldStorageConfig::RamOnly => {
354 Arc::new(ChunkStorage::RamOnly(RamOnlyStorage::empty_world()))
355 }
356 };
357
358 let path = config.level_data_path.as_deref().map(Path::new);
361 let saved_data = SavedDataManager::new(path);
362 let mut level_data =
363 LevelDataManager::new(path, seed, config.difficulty, config.generation_settings)
364 .await?;
365 if level_data.is_dirty() {
366 level_data.save().await?;
367 }
368 let persistent_chunk_tickets: PersistentChunkTickets = saved_data
369 .load_or_default(saved_data_names::CHUNK_TICKETS)
370 .await?;
371 let timed_chunk_tickets = TimedChunkTickets::from_persistent(persistent_chunk_tickets);
372 let world_border = WorldBorder::new(level_data.data().world_border)
373 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
374 let mut weather = Weather::default();
385 if level_data.is_raining() {
386 weather.rain_level = 1.0;
387 if level_data.is_thundering() {
388 weather.thunder_level = 1.0;
389 }
390 }
391
392 Ok(Arc::new_cyclic(|weak_self: &Weak<World>| {
393 let chunk_map = Arc::new(ChunkMap::new_with_storage_and_timed_tickets(
394 chunk_runtime,
395 weak_self.clone(),
396 dimension_type,
397 sea_level,
398 storage,
399 config.generator,
400 generation_pool,
401 chunk_encoding_pool,
402 timed_chunk_tickets,
403 ));
404 chunk_map.start_generation_refill_loop();
405
406 Self {
407 chunk_map,
408 players: PlayerMap::new(),
409 player_area_map: PlayerAreaMap::new(),
410 key,
411 dimension_type,
412 level_data: SyncRwLock::new(level_data),
413 saved_data,
414 world_border: SyncMutex::new(world_border),
415 view_distance,
416 simulation_distance,
417 compression,
418 is_flat,
419 sea_level,
420 default_gamemode,
421 tick_runs_normally: AtomicBool::new(true),
422 handling_tick: AtomicBool::new(false),
423 block_events: SyncMutex::new(BlockEventQueue::default()),
424 neighbor_updater: CollectingNeighborUpdater::new(max_chained_neighbor_updates),
425 entity_manager: WorldEntityManager::new(),
426 block_entity_tickers: block_entity_ticker::WorldBlockEntityTickers::new(),
427 game_event_listener_count: GameEventListenerCount::shared(),
428 entity_tracker: EntityTracker::new(),
429 navigating_mobs: NavigatingMobTracker::new(),
430 weather: SyncMutex::new(weather),
431 redstone_torch_toggles: SyncMutex::new(
432 redstone::RedstoneTorchToggleTracker::default(),
433 ),
434 scheduled_ticks: tick_scheduler::WorldTickScheduler::new(),
435 scheduled_block_ticks_this_tick: SyncMutex::new(None),
436 scheduled_fluid_ticks_this_tick: SyncMutex::new(None),
437 poi_storage: SyncMutex::new(PointOfInterestStorage::new()),
438 pending_world_changes: SyncMutex::new(Vec::new()),
439 }
440 }))
441 }
442
443 #[expect(
445 clippy::await_holding_lock,
446 reason = "holding the write lock across await is safe here because it only happens during shutdown"
447 )]
448 pub async fn cleanup(&self, total_saved: &mut usize) {
449 self.sync_world_border_to_level_data();
450 match self.level_data.write().save().await {
451 Ok(()) => log::info!("World {} level data saved successfully", self.key),
452 Err(e) => log::error!("Failed to save world level data: {e}"),
453 }
454
455 let chunk_tickets = self.chunk_map.persistent_chunk_tickets();
456 match self
457 .saved_data
458 .save(saved_data_names::CHUNK_TICKETS, &chunk_tickets)
459 .await
460 {
461 Ok(()) => log::info!("World {} saved chunk ticket data successfully", self.key),
462 Err(e) => log::error!("Failed to save world chunk ticket data: {e}"),
463 }
464
465 match self.save_all_chunks().await {
466 Ok(count) => *total_saved += count,
467 Err(e) => log::error!("Failed to save world chunks: {e}"),
468 }
469 }
470
471 #[must_use]
473 pub fn domain(&self) -> &str {
474 self.key.namespace.as_ref()
475 }
476
477 #[tracing::instrument(level = "trace", skip(self), name = "world_game_tick")]
484 #[expect(
485 clippy::too_many_lines,
486 reason = "world tick orchestration keeps vanilla subsystem order explicit"
487 )]
488 pub fn tick_game(
489 self: &Arc<Self>,
490 tick_count: u64,
491 runs_normally: bool,
492 ) -> WorldGameTickTimings {
493 let world_start = Instant::now();
494 let lookup_cache_scope = GameplayChunkLookupCacheScope::enter(&self.chunk_map);
495 self.handling_tick.store(true, Ordering::Relaxed);
496 self.set_tick_runs_normally(runs_normally);
497 if runs_normally {
498 self.tick_world_border();
499 self.tick_weather();
500 self.tick_time();
501 }
502
503 let random_tick_speed = self.get_game_rule(&RANDOM_TICK_SPEED) as u32;
504
505 let mut chunk_map_timings =
506 self.chunk_map
507 .tick_game(self, tick_count, random_tick_speed, runs_normally);
508
509 if runs_normally {
510 let _span = tracing::trace_span!("block_events").entered();
511 self.run_block_events();
512 }
513
514 self.handling_tick.store(false, Ordering::Relaxed);
516
517 let entity_tick = {
518 let _span = tracing::trace_span!("entity_tick").entered();
519 let start = Instant::now();
520 let dirty_chunks = self
521 .entity_manager
522 .tick_entities(tick_count as i32, runs_normally);
523 for chunk in dirty_chunks {
524 self.mark_chunk_dirty(chunk);
525 }
526 start.elapsed()
527 };
528
529 {
530 let _span = tracing::trace_span!("block_entities").entered();
531 let start = Instant::now();
532 self.block_entity_tickers.tick(self, runs_normally);
533 chunk_map_timings.tick_block_entities = start.elapsed();
534 }
535
536 {
537 let _span = tracing::trace_span!("entity_tracker_send_changes").entered();
538 self.entity_tracker.send_changes(
539 |chunk| self.get_packet_tracking_players(chunk),
540 |player_id| self.players.get_by_entity_id(player_id),
541 EntityChangeSenders {
542 movement: |entity_id, packet| {
543 self.broadcast_movement_sync_to_entity_trackers(entity_id, packet, None);
544 },
545 self_movement: |player_id, packet| {
546 let Some(encoded) = self.encode_movement_sync_packet(packet) else {
547 return;
548 };
549 let Some(player) = self.players.get_by_entity_id(player_id) else {
550 return;
551 };
552 player.connection.send_encoded(encoded);
553 },
554 entity_data: |entity_id, dirty_entity_data| {
555 let packet = CSetEntityData::new(entity_id, dirty_entity_data);
556 let Ok(encoded) = EncodedPacket::from_bare(
557 packet,
558 self.compression,
559 ConnectionProtocol::Play,
560 ) else {
561 return;
562 };
563 self.broadcast_to_entity_trackers_encoded(entity_id, encoded.clone(), None);
564 if let Some(player) = self.players.get_by_entity_id(entity_id) {
565 player.connection.send_encoded(encoded);
566 }
567 },
568 attributes: |entity_id, dirty_attributes| {
569 let packet = CUpdateAttributes::new(entity_id, dirty_attributes);
570 let Ok(encoded) = EncodedPacket::from_bare(
571 packet,
572 self.compression,
573 ConnectionProtocol::Play,
574 ) else {
575 return;
576 };
577 self.broadcast_to_entity_trackers_encoded(entity_id, encoded.clone(), None);
578 if let Some(player) = self.players.get_by_entity_id(entity_id) {
579 player.connection.send_encoded(encoded);
580 }
581 },
582 mob_effects: |player_id, packet| {
583 let Some(player) = self.players.get_by_entity_id(player_id) else {
584 return;
585 };
586 match packet {
587 MobEffectSyncPacket::Update(packet) => player.send_packet(packet),
588 MobEffectSyncPacket::Remove(packet) => player.send_packet(packet),
589 }
590 },
591 equipment: |entity_id, packet: CSetEquipment| {
592 let Ok(encoded) = EncodedPacket::from_bare(
593 packet,
594 self.compression,
595 ConnectionProtocol::Play,
596 ) else {
597 return;
598 };
599 self.broadcast_to_entity_trackers_encoded(entity_id, encoded, None);
600 },
601 passengers: |player_id, packet| {
602 if let Some(player) = self.players.get_by_entity_id(player_id) {
603 player.send_packet(packet);
604 }
605 },
606 entity_link: |entity_id, packet: CSetEntityLink| {
607 let Ok(encoded) = EncodedPacket::from_bare(
608 packet,
609 self.compression,
610 ConnectionProtocol::Play,
611 ) else {
612 return;
613 };
614 self.broadcast_to_entity_trackers_encoded(entity_id, encoded, None);
615 },
616 },
617 );
618 }
619
620 chunk_map_timings.lookup_cache = lookup_cache_scope.finish();
621 WorldGameTickTimings {
622 elapsed: world_start.elapsed(),
623 chunk_map: chunk_map_timings,
624 entity_tick,
625 }
626 }
627
628 pub async fn save_all_chunks(&self) -> io::Result<usize> {
633 self.chunk_map.save_all_chunks().await
634 }
635}
636
637impl LevelReader for World {
638 fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
639 Self::get_block_state(self, pos)
640 }
641
642 fn get_block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
643 Self::get_block_entity(self, pos)
644 }
645
646 fn is_face_sturdy_for(
647 &self,
648 state: BlockStateId,
649 pos: BlockPos,
650 direction: Direction,
651 support_type: SupportType,
652 ) -> bool {
653 BLOCK_BEHAVIORS
654 .get_behavior(state.get_block())
655 .is_face_sturdy(state, self, pos, direction, support_type)
656 }
657
658 fn raw_brightness(&self, pos: BlockPos, sky_darkening: u8) -> u8 {
659 let sky_light = if self.dimension_type.has_skylight {
660 self.light_value_at(LightLayer::Sky, pos)
661 .saturating_sub(sky_darkening)
662 } else {
663 0
664 };
665
666 if sky_light == MAX_LIGHT_LEVEL {
667 return MAX_LIGHT_LEVEL;
668 }
669
670 sky_light.max(self.light_value_at(LightLayer::Block, pos))
671 }
672
673 fn can_see_sky(&self, pos: BlockPos) -> bool {
674 Self::can_see_sky(self, pos)
675 }
676
677 fn ambient_light(&self) -> f32 {
678 self.dimension_type.ambient_light
679 }
680
681 fn min_y(&self) -> i32 {
682 self.get_min_y()
683 }
684
685 fn height(&self) -> i32 {
686 self.get_height()
687 }
688}
689
690impl LevelReader for Arc<World> {
691 fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
692 self.as_ref().get_block_state(pos)
693 }
694
695 fn get_block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
696 self.as_ref().get_block_entity(pos)
697 }
698
699 fn is_face_sturdy_for(
700 &self,
701 state: BlockStateId,
702 pos: BlockPos,
703 direction: Direction,
704 support_type: SupportType,
705 ) -> bool {
706 self.as_ref()
707 .is_face_sturdy_for(state, pos, direction, support_type)
708 }
709
710 fn raw_brightness(&self, pos: BlockPos, sky_darkening: u8) -> u8 {
711 self.as_ref().raw_brightness(pos, sky_darkening)
712 }
713
714 fn can_see_sky(&self, pos: BlockPos) -> bool {
715 self.as_ref().can_see_sky(pos)
716 }
717
718 fn ambient_light(&self) -> f32 {
719 self.as_ref().ambient_light()
720 }
721
722 fn min_y(&self) -> i32 {
723 self.as_ref().get_min_y()
724 }
725
726 fn height(&self) -> i32 {
727 self.as_ref().get_height()
728 }
729}
730
731impl ScheduledTickAccess for Arc<World> {
732 fn fluid_tick_delay(&self, fluid: FluidRef) -> i32 {
733 FLUID_BEHAVIORS.get_behavior(fluid).tick_delay(self)
734 }
735
736 fn schedule_block_tick_default(&self, pos: BlockPos, block: BlockRef, delay: i32) -> bool {
737 self.as_ref().schedule_block_tick_default(pos, block, delay);
738 true
739 }
740
741 fn has_scheduled_block_tick(&self, pos: BlockPos, block: BlockRef) -> bool {
742 self.as_ref().has_scheduled_block_tick(pos, block)
743 }
744
745 fn will_tick_block_this_tick(&self, pos: BlockPos, block: BlockRef) -> bool {
746 self.as_ref().will_tick_block_this_tick(pos, block)
747 }
748
749 fn schedule_fluid_tick_default(&self, pos: BlockPos, fluid: FluidRef, delay: i32) -> bool {
750 self.as_ref().schedule_fluid_tick_default(pos, fluid, delay);
751 true
752 }
753
754 fn will_tick_fluid_this_tick(&self, pos: BlockPos, fluid: FluidRef) -> bool {
755 self.as_ref().will_tick_fluid_this_tick(pos, fluid)
756 }
757}
758
759impl LevelAccessor for Arc<World> {
760 fn set_block_state(&self, pos: BlockPos, state: BlockStateId, flags: UpdateFlags) -> bool {
761 self.set_block(pos, state, flags)
762 }
763
764 fn play_block_sound(
765 &self,
766 sound: SoundEventRef,
767 pos: BlockPos,
768 volume: f32,
769 pitch: f32,
770 exclude: Option<i32>,
771 ) {
772 self.as_ref()
773 .play_block_sound(sound, pos, volume, pitch, exclude);
774 }
775
776 fn game_event(&self, event: GameEventRef, pos: BlockPos, context: &GameEventContext<'_>) {
777 World::game_event(self, event, pos, context);
778 }
779}