Skip to main content

steel_core/world/
mod.rs

1//! This module contains the `World` struct, which represents a world.
2
3use 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;
115/// Vanilla game-event contexts, listeners, and dispatch storage.
116pub 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/// Timing information for a world game tick.
175#[derive(Debug)]
176pub struct WorldGameTickTimings {
177    /// Total time for this world's tick.
178    pub elapsed: Duration,
179    /// Chunk map game tick timings.
180    pub chunk_map: ChunkMapGameTickTimings,
181    /// Time spent ticking entities.
182    pub entity_tick: Duration,
183}
184
185/// Result of replacing a block only when its current state still matches a prior read.
186#[must_use]
187#[derive(Clone, Copy, Debug, Eq, PartialEq)]
188pub enum ConditionalBlockSetResult {
189    /// The expected state was claimed and replaced.
190    Changed,
191    /// The expected state already equals the requested state, so no callbacks ran.
192    Unchanged,
193    /// The current state did not match the caller's expected state.
194    Stale(BlockStateId),
195    /// The position is invalid, the chunk is unavailable, or the update limit was exhausted.
196    Unavailable,
197}
198
199/// Configuration for creating a new world.
200#[derive(Clone)]
201pub struct WorldConfig {
202    /// Storage configuration for chunk persistence.
203    pub storage: WorldStorageConfig,
204    /// Directory for level data. `None` means level data is ephemeral.
205    pub level_data_path: Option<String>,
206    /// World generator.
207    pub generator: Arc<ChunkGeneratorType>,
208    /// Generator metadata persisted for startup compatibility checks.
209    pub generation_settings: WorldGenerationSettings,
210    /// Server view distance (maximum chunk radius).
211    pub view_distance: u8,
212    /// Server simulation distance.
213    pub simulation_distance: u8,
214    /// Maximum queued neighbor-update tasks in one chained run; negative means unlimited.
215    pub max_chained_neighbor_updates: i32,
216    /// Compression settings for encoding broadcast packets.
217    pub compression: Option<CompressionInfo>,
218    /// Whether the world should be marked as flat in login/respawn packets.
219    pub is_flat: bool,
220    /// Sea level sent in login/respawn packets.
221    pub sea_level: i32,
222    /// Default game mode for first-visit player data.
223    pub default_gamemode: GameType,
224    /// Difficulty used when creating new level data.
225    pub difficulty: Difficulty,
226}
227
228/// A struct that represents a world.
229pub struct World {
230    /// The chunk map of the world.
231    pub chunk_map: Arc<ChunkMap>,
232    /// All players in the world with dual indexing by UUID and entity ID.
233    pub players: PlayerMap,
234    /// Spatial index for player proximity queries.
235    pub player_area_map: PlayerAreaMap,
236    /// Loaded world identifier (`domain:world`).
237    pub key: Identifier,
238    /// Vanilla dimension type for this loaded world.
239    ///
240    /// Vanilla often calls loaded worlds "dimensions". In Steel, `World` is the
241    /// loaded world instance and `dimension_type` is the vanilla registry entry
242    /// controlling height, skylight, ceiling, water evaporation, etc.
243    pub dimension_type: DimensionTypeRef,
244    /// Level data manager for persistent world state.
245    pub level_data: SyncRwLock<LevelDataManager>,
246    /// Per-world saved data storage.
247    pub(crate) saved_data: SavedDataManager,
248    /// Runtime world border state.
249    world_border: SyncMutex<WorldBorder>,
250    /// Server view distance (maximum chunk radius).
251    pub view_distance: u8,
252    /// Server simulation distance.
253    pub simulation_distance: u8,
254    /// Compression settings for encoding broadcast packets.
255    pub compression: Option<CompressionInfo>,
256    /// Whether the world should be marked as flat in login/respawn packets.
257    pub is_flat: bool,
258    /// Sea level sent in login/respawn packets.
259    pub sea_level: i32,
260    /// Default game mode for first-visit player data.
261    pub default_gamemode: GameType,
262    /// Whether the tick rate is running normally (not frozen/paused).
263    /// When false, movement validation checks are skipped.
264    tick_runs_normally: AtomicBool,
265    /// Whether vanilla's scheduled/chunk/block-event tick phase is active.
266    handling_tick: AtomicBool,
267    /// Ordered, duplicate-suppressing server block events awaiting execution.
268    block_events: SyncMutex<BlockEventQueue>,
269    /// Vanilla collecting neighbor updater shared by all live block mutations.
270    neighbor_updater: CollectingNeighborUpdater,
271    /// Central runtime entity ownership and lookup.
272    entity_manager: WorldEntityManager,
273    /// World-global ordered block-entity ticker phase.
274    block_entity_tickers: block_entity_ticker::WorldBlockEntityTickers,
275    /// Physical entries retained by this world's chunk-owned game-event registries.
276    game_event_listener_count: Arc<GameEventListenerCount>,
277    /// Entity tracker for managing which players can see which entities.
278    entity_tracker: EntityTracker,
279    /// Runtime IDs for pathfinder mobs currently visible to the active world.
280    navigating_mobs: NavigatingMobTracker,
281    /// Weather Data needed for animating starting and stopping of rain clientside
282    pub weather: SyncMutex<Weather>,
283    /// Per-level recent toggle history used by vanilla redstone-torch burnout.
284    redstone_torch_toggles: SyncMutex<redstone::RedstoneTorchToggleTracker>,
285    /// World registration and sparse head index for chunk-owned scheduled ticks.
286    scheduled_ticks: tick_scheduler::WorldTickScheduler,
287    /// Published block batch used by `willTickThisTick` queries during callbacks.
288    scheduled_block_ticks_this_tick:
289        SyncMutex<Option<Arc<tick_scheduler::ScheduledTickRunBatch<BlockRef>>>>,
290    /// Published fluid batch used by `willTickThisTick` queries during callbacks.
291    scheduled_fluid_ticks_this_tick:
292        SyncMutex<Option<Arc<tick_scheduler::ScheduledTickRunBatch<FluidRef>>>>,
293    /// Point of interest storage for efficient spatial queries of special blocks.
294    pub poi_storage: SyncMutex<PointOfInterestStorage>,
295    /// World-change requests queued by world-local ticks for server safe-point processing.
296    pending_world_changes: SyncMutex<Vec<(SharedEntity, WorldChangeRequest)>>,
297}
298
299impl World {
300    /// Creates a new world with custom configuration.
301    ///
302    /// This allows specifying storage backend (disk or RAM-only) and other options.
303    /// Uses `Arc::new_cyclic` to create a cyclic reference between
304    /// the World and its `ChunkMap`'s `WorldGenContext`.
305    ///
306    /// # Arguments
307    /// * `chunk_runtime` - The Tokio runtime for chunk operations
308    /// * `dimension_type` - Vanilla dimension type (overworld, nether, end)
309    /// * `seed` - The world seed
310    /// * `config` - World configuration including storage options
311    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        // Create storage backend based on config
349        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        // Create or skip level data based on config
359
360        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 generator = Arc::new(ChunkGeneratorType::Flat(FlatChunkGenerator::new(
375        //     REGISTRY
376        //         .blocks
377        //         .get_default_state_id(vanilla_blocks::BEDROCK), // Bedrock
378        //     REGISTRY.blocks.get_default_state_id(vanilla_blocks::DIRT), // Dirt
379        //     REGISTRY
380        //         .blocks
381        //         .get_default_state_id(vanilla_blocks::GRASS_BLOCK), // Grass Block
382        // )));
383
384        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    /// Cleans up the world by saving all chunks.
444    #[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    /// Returns the domain this loaded world belongs to.
472    #[must_use]
473    pub fn domain(&self) -> &str {
474        self.key.namespace.as_ref()
475    }
476
477    /// Game tick: weather, time, chunk game tick (broadcasts + random/scheduled ticks),
478    /// and player logic (without chunk sending).
479    ///
480    /// * `tick_count` - The current tick number
481    /// * `runs_normally` - Whether game elements (random ticks, entities) should run.
482    ///   When false (frozen), only essential operations like chunk loading run.
483    #[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        // Vanilla clears this before ticking entities and block entities.
515        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    /// Saves all dirty chunks in this world to disk.
629    ///
630    /// This should be called during graceful shutdown.
631    /// Returns the number of chunks saved.
632    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}