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_storage::{ChunkTicketStorage, PersistentChunkTickets};
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::{
101        GameTime, GameTimeSource, LevelDataManager, RespawnData, WorldGenerationSettings,
102    },
103    player::{LastSeen, Player, connection::NetworkConnection},
104    poi::PointOfInterestStorage,
105};
106
107mod block_entity_ticker;
108mod block_event;
109mod block_region;
110mod block_updates;
111mod border;
112mod broadcasts;
113pub(crate) mod clock;
114mod entity_management;
115mod environment;
116mod events;
117/// Vanilla game-event contexts, listeners, and dispatch storage.
118pub mod game_event;
119mod level_effects;
120mod level_reader;
121mod player_index;
122pub(crate) mod player_spawn_finder;
123mod portals;
124mod properties;
125mod raycast;
126mod redstone;
127mod signal_getter;
128mod sleep;
129mod sleep_status;
130mod spawn;
131pub mod tick_scheduler;
132mod weather;
133mod world_entities;
134
135#[cfg(test)]
136mod tests;
137
138pub use crate::config::WorldStorageConfig;
139use crate::worldgen::generators::vanilla::fuzzed_biome_at_block;
140use crate::worldgen::{ChunkGenerator, ChunkGeneratorType};
141use block_event::BlockEventQueue;
142pub(crate) use block_region::{BlockRegionBounds, MAX_BLOCK_REGION_WORKSET_SLOTS};
143use block_updates::CollectingNeighborUpdater;
144pub use border::WorldBorderError;
145pub(crate) use border::{MAX_CENTER_COORDINATE, MAX_SIZE};
146use border::{WorldBorder, WorldBorderSnapshot};
147use entity_management::NavigatingMobTracker;
148#[cfg(test)]
149use entity_management::nearest_player_distance_in_range;
150pub use level_reader::{LevelAccessor, LevelReader, ScheduledTickAccess};
151pub use player_index::{PlayerAreaMap, PlayerMap};
152pub use raycast::{ClipBlockShape, ClipFluid, ClipHitResult, RaytraceAction};
153#[cfg(test)]
154pub(crate) use signal_getter::get_best_neighbor_signal;
155pub use signal_getter::{SignalGetter, SignalQueryContext};
156pub(crate) use signal_getter::{get_control_input_signal, get_signal, is_redstone_conductor};
157pub use tick_scheduler::ScheduledTick;
158
159#[cfg(test)]
160use level_effects::sound_is_within_range;
161#[cfg(test)]
162use portals::{
163    closest_portal_candidate, nether_portal_creation_scan_origin, nether_portal_frame_offset_pos,
164};
165
166const fn initialize_border_packet(snapshot: WorldBorderSnapshot) -> CInitializeBorder {
167    CInitializeBorder {
168        new_center_x: snapshot.center_x,
169        new_center_z: snapshot.center_z,
170        old_size: snapshot.old_size,
171        new_size: snapshot.new_size,
172        lerp_time: snapshot.lerp_time,
173        new_absolute_max_size: snapshot.absolute_max_size,
174        warning_blocks: snapshot.warning_blocks,
175        warning_time: snapshot.warning_time,
176    }
177}
178
179/// Timing information for a world game tick.
180#[derive(Debug)]
181pub struct WorldGameTickTimings {
182    /// Total time for this world's tick.
183    pub elapsed: Duration,
184    /// Chunk map game tick timings.
185    pub chunk_map: ChunkMapGameTickTimings,
186    /// Time spent ticking entities.
187    pub entity_tick: Duration,
188}
189
190/// Result of replacing a block only when its current state still matches a prior read.
191#[must_use]
192#[derive(Clone, Copy, Debug, Eq, PartialEq)]
193pub enum ConditionalBlockSetResult {
194    /// The expected state was claimed and replaced.
195    Changed,
196    /// The expected state already equals the requested state, so no callbacks ran.
197    Unchanged,
198    /// The current state did not match the caller's expected state.
199    Stale(BlockStateId),
200    /// The position is invalid, the chunk is unavailable, or the update limit was exhausted.
201    Unavailable,
202}
203
204/// Configuration for creating a new world.
205#[derive(Clone)]
206pub struct WorldConfig {
207    /// Domain game-time authority, bound during construction.
208    pub game_time_source: GameTimeSource,
209    /// Storage configuration for chunk persistence.
210    pub storage: WorldStorageConfig,
211    /// Directory for level data. `None` means level data is ephemeral.
212    pub level_data_path: Option<String>,
213    /// World generator.
214    pub generator: Arc<ChunkGeneratorType>,
215    /// Generator metadata persisted for startup compatibility checks.
216    pub generation_settings: WorldGenerationSettings,
217    /// Server view distance (maximum chunk radius).
218    pub view_distance: u8,
219    /// Server simulation distance.
220    pub simulation_distance: u8,
221    /// Maximum queued neighbor-update tasks in one chained run; negative means unlimited.
222    pub max_chained_neighbor_updates: i32,
223    /// Compression settings for encoding broadcast packets.
224    pub compression: Option<CompressionInfo>,
225    /// Whether the world should be marked as flat in login/respawn packets.
226    pub is_flat: bool,
227    /// Sea level sent in login/respawn packets.
228    pub sea_level: i32,
229    /// Default game mode for first-visit player data.
230    pub default_gamemode: GameType,
231    /// Difficulty used when creating new level data.
232    pub difficulty: Difficulty,
233}
234
235/// A struct that represents a world.
236pub struct World {
237    /// The chunk map of the world.
238    pub chunk_map: Arc<ChunkMap>,
239    /// All players in the world with dual indexing by UUID and entity ID.
240    pub players: PlayerMap,
241    /// Spatial index for player proximity queries.
242    pub player_area_map: PlayerAreaMap,
243    /// Loaded world identifier (`domain:world`).
244    pub key: Identifier,
245    /// Vanilla dimension type for this loaded world.
246    ///
247    /// Vanilla often calls loaded worlds "dimensions". In Steel, `World` is the
248    /// loaded world instance and `dimension_type` is the vanilla registry entry
249    /// controlling height, skylight, ceiling, water evaporation, etc.
250    pub dimension_type: DimensionTypeRef,
251    /// Level data manager for persistent world state.
252    pub level_data: SyncRwLock<LevelDataManager>,
253    pub(crate) game_time: Arc<GameTime>,
254    /// Per-world saved data storage.
255    pub(crate) saved_data: SavedDataManager,
256    /// Runtime world border state.
257    world_border: SyncMutex<WorldBorder>,
258    /// Vanilla sleeping player counts for night-skip checks.
259    sleep_status: SyncMutex<sleep_status::SleepStatus>,
260    /// Server view distance (maximum chunk radius).
261    pub view_distance: u8,
262    /// Server simulation distance.
263    pub simulation_distance: u8,
264    /// Compression settings for encoding broadcast packets.
265    pub compression: Option<CompressionInfo>,
266    /// Whether the world should be marked as flat in login/respawn packets.
267    pub is_flat: bool,
268    /// Sea level sent in login/respawn packets.
269    pub sea_level: i32,
270    /// Default game mode for first-visit player data.
271    pub default_gamemode: GameType,
272    /// Whether the tick rate is running normally (not frozen/paused).
273    /// When false, movement validation checks are skipped.
274    tick_runs_normally: AtomicBool,
275    /// Whether vanilla's scheduled/chunk/block-event tick phase is active.
276    handling_tick: AtomicBool,
277    /// Ordered, duplicate-suppressing server block events awaiting execution.
278    block_events: SyncMutex<BlockEventQueue>,
279    /// Vanilla collecting neighbor updater shared by all live block mutations.
280    neighbor_updater: CollectingNeighborUpdater,
281    /// Central runtime entity ownership and lookup.
282    entity_manager: WorldEntityManager,
283    /// World-global ordered block-entity ticker phase.
284    block_entity_tickers: block_entity_ticker::WorldBlockEntityTickers,
285    /// Physical entries retained by this world's chunk-owned game-event registries.
286    game_event_listener_count: Arc<GameEventListenerCount>,
287    /// Entity tracker for managing which players can see which entities.
288    entity_tracker: EntityTracker,
289    /// Runtime IDs for pathfinder mobs currently visible to the active world.
290    navigating_mobs: NavigatingMobTracker,
291    /// Weather Data needed for animating starting and stopping of rain clientside
292    pub weather: SyncMutex<Weather>,
293    /// Per-level recent toggle history used by vanilla redstone-torch burnout.
294    redstone_torch_toggles: SyncMutex<redstone::RedstoneTorchToggleTracker>,
295    /// World registration and sparse head index for chunk-owned scheduled ticks.
296    scheduled_ticks: tick_scheduler::WorldTickScheduler,
297    /// Published block batch used by `willTickThisTick` queries during callbacks.
298    scheduled_block_ticks_this_tick:
299        SyncMutex<Option<Arc<tick_scheduler::ScheduledTickRunBatch<BlockRef>>>>,
300    /// Published fluid batch used by `willTickThisTick` queries during callbacks.
301    scheduled_fluid_ticks_this_tick:
302        SyncMutex<Option<Arc<tick_scheduler::ScheduledTickRunBatch<FluidRef>>>>,
303    /// Point of interest storage for efficient spatial queries of special blocks.
304    pub poi_storage: SyncMutex<PointOfInterestStorage>,
305    /// World-change requests queued by world-local ticks for server safe-point processing.
306    pending_world_changes: SyncMutex<Vec<(SharedEntity, WorldChangeRequest)>>,
307}
308
309impl World {
310    /// Creates a new world with custom configuration.
311    ///
312    /// This allows specifying storage backend (disk or RAM-only) and other options.
313    /// Uses `Arc::new_cyclic` to create a cyclic reference between
314    /// the World and its `ChunkMap`'s `WorldGenContext`.
315    ///
316    /// # Arguments
317    /// * `chunk_runtime` - The Tokio runtime for chunk operations
318    /// * `dimension_type` - Vanilla dimension type (overworld, nether, end)
319    /// * `seed` - The world seed
320    /// * `config` - World configuration including storage options
321    pub async fn new_with_config(
322        chunk_runtime: Arc<Runtime>,
323        key: Identifier,
324        dimension_type: DimensionTypeRef,
325        seed: i64,
326        config: WorldConfig,
327        generation_pool: Arc<rayon::ThreadPool>,
328    ) -> io::Result<Arc<Self>> {
329        let chunk_encoding_pool = Arc::clone(&generation_pool);
330        Self::new_with_config_and_encoding_pool(
331            chunk_runtime,
332            key,
333            dimension_type,
334            seed,
335            config,
336            generation_pool,
337            chunk_encoding_pool,
338        )
339        .await
340    }
341
342    pub(crate) async fn new_with_config_and_encoding_pool(
343        chunk_runtime: Arc<Runtime>,
344        key: Identifier,
345        dimension_type: DimensionTypeRef,
346        seed: i64,
347        config: WorldConfig,
348        generation_pool: Arc<rayon::ThreadPool>,
349        chunk_encoding_pool: Arc<rayon::ThreadPool>,
350    ) -> io::Result<Arc<Self>> {
351        let view_distance = config.view_distance;
352        let simulation_distance = config.simulation_distance;
353        let max_chained_neighbor_updates = config.max_chained_neighbor_updates;
354        let compression = config.compression;
355        let is_flat = config.is_flat;
356        let sea_level = config.sea_level;
357        let default_gamemode = config.default_gamemode;
358        // Create storage backend based on config
359        let storage: Arc<ChunkStorage> = match &config.storage {
360            WorldStorageConfig::Disk { path } => {
361                Arc::new(ChunkStorage::Disk(RegionManager::new(path.clone())))
362            }
363            WorldStorageConfig::RamOnly => {
364                Arc::new(ChunkStorage::RamOnly(RamOnlyStorage::empty_world()))
365            }
366        };
367
368        // Create or skip level data based on config
369
370        let path = config.level_data_path.as_deref().map(Path::new);
371        let saved_data = SavedDataManager::new(path);
372        let mut level_data = LevelDataManager::new(
373            path,
374            seed,
375            config.difficulty,
376            config.generation_settings,
377            config.game_time_source,
378        )
379        .await?;
380        if level_data.is_dirty() {
381            level_data.save().await?;
382        }
383        let persistent_chunk_tickets: PersistentChunkTickets = saved_data
384            .load_or_default(saved_data_names::CHUNK_TICKETS)
385            .await?;
386        let ticket_storage = ChunkTicketStorage::from_persistent(persistent_chunk_tickets)
387            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
388        let world_border = WorldBorder::new(level_data.data().world_border)
389            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
390        // let generator = Arc::new(ChunkGeneratorType::Flat(FlatChunkGenerator::new(
391        //     REGISTRY
392        //         .blocks
393        //         .get_default_state_id(vanilla_blocks::BEDROCK), // Bedrock
394        //     REGISTRY.blocks.get_default_state_id(vanilla_blocks::DIRT), // Dirt
395        //     REGISTRY
396        //         .blocks
397        //         .get_default_state_id(vanilla_blocks::GRASS_BLOCK), // Grass Block
398        // )));
399
400        let mut weather = Weather::default();
401        if level_data.is_raining() {
402            weather.rain_level = 1.0;
403            if level_data.is_thundering() {
404                weather.thunder_level = 1.0;
405            }
406        }
407
408        Ok(Arc::new_cyclic(|weak_self: &Weak<World>| {
409            let chunk_map = Arc::new(ChunkMap::new_with_storage_and_ticket_storage(
410                chunk_runtime,
411                weak_self.clone(),
412                dimension_type,
413                sea_level,
414                storage,
415                config.generator,
416                generation_pool,
417                chunk_encoding_pool,
418                view_distance,
419                simulation_distance,
420                ticket_storage,
421            ));
422            chunk_map.start_generation_refill_loop();
423
424            Self {
425                chunk_map,
426                players: PlayerMap::new(),
427                player_area_map: PlayerAreaMap::new(),
428                key,
429                dimension_type,
430                game_time: level_data.game_time_handle(),
431                level_data: SyncRwLock::new(level_data),
432                saved_data,
433                world_border: SyncMutex::new(world_border),
434                sleep_status: SyncMutex::new(sleep_status::SleepStatus::default()),
435                view_distance,
436                simulation_distance,
437                compression,
438                is_flat,
439                sea_level,
440                default_gamemode,
441                tick_runs_normally: AtomicBool::new(true),
442                handling_tick: AtomicBool::new(false),
443                block_events: SyncMutex::new(BlockEventQueue::default()),
444                neighbor_updater: CollectingNeighborUpdater::new(max_chained_neighbor_updates),
445                entity_manager: WorldEntityManager::new(),
446                block_entity_tickers: block_entity_ticker::WorldBlockEntityTickers::new(),
447                game_event_listener_count: GameEventListenerCount::shared(),
448                entity_tracker: EntityTracker::new(),
449                navigating_mobs: NavigatingMobTracker::new(),
450                weather: SyncMutex::new(weather),
451                redstone_torch_toggles: SyncMutex::new(
452                    redstone::RedstoneTorchToggleTracker::default(),
453                ),
454                scheduled_ticks: tick_scheduler::WorldTickScheduler::new(),
455                scheduled_block_ticks_this_tick: SyncMutex::new(None),
456                scheduled_fluid_ticks_this_tick: SyncMutex::new(None),
457                poi_storage: SyncMutex::new(PointOfInterestStorage::new()),
458                pending_world_changes: SyncMutex::new(Vec::new()),
459            }
460        }))
461    }
462
463    /// Cleans up the world by saving all chunks.
464    #[expect(
465        clippy::await_holding_lock,
466        reason = "holding the write lock across await is safe here because it only happens during shutdown"
467    )]
468    pub async fn cleanup(&self, total_saved: &mut usize) {
469        self.sync_world_border_to_level_data();
470        match self.level_data.write().save().await {
471            Ok(()) => log::info!("World {} level data saved successfully", self.key),
472            Err(e) => log::error!("Failed to save world level data: {e}"),
473        }
474
475        let chunk_tickets = self.chunk_map.persistent_chunk_tickets();
476        match self
477            .saved_data
478            .save(saved_data_names::CHUNK_TICKETS, &chunk_tickets)
479            .await
480        {
481            Ok(()) => log::info!("World {} saved chunk ticket data successfully", self.key),
482            Err(e) => log::error!("Failed to save world chunk ticket data: {e}"),
483        }
484
485        match self.save_all_chunks().await {
486            Ok(count) => *total_saved += count,
487            Err(e) => log::error!("Failed to save world chunks: {e}"),
488        }
489    }
490
491    /// Returns the domain this loaded world belongs to.
492    #[must_use]
493    pub fn domain(&self) -> &str {
494        self.key.namespace.as_ref()
495    }
496
497    /// Game tick: weather, time, chunk game tick (broadcasts + random/scheduled ticks),
498    /// and player logic (without chunk sending).
499    ///
500    /// * `tick_count` - The current tick number
501    /// * `runs_normally` - Whether game elements (random ticks, entities) should run.
502    ///   When false (frozen), only essential operations like chunk loading run.
503    #[tracing::instrument(level = "trace", skip(self), name = "world_game_tick")]
504    #[expect(
505        clippy::too_many_lines,
506        reason = "world tick orchestration keeps vanilla subsystem order explicit"
507    )]
508    pub fn tick_game(
509        self: &Arc<Self>,
510        tick_count: u64,
511        runs_normally: bool,
512    ) -> WorldGameTickTimings {
513        let world_start = Instant::now();
514        let lookup_cache_scope = GameplayChunkLookupCacheScope::enter(&self.chunk_map);
515        self.handling_tick.store(true, Ordering::Relaxed);
516        self.set_tick_runs_normally(runs_normally);
517        if runs_normally {
518            self.tick_world_border();
519            self.tick_weather();
520        }
521        self.tick_sleeping_players();
522        if runs_normally {
523            self.tick_time();
524        }
525
526        let random_tick_speed = self.get_game_rule(&RANDOM_TICK_SPEED) as u32;
527
528        let early_lookup_stats = lookup_cache_scope.finish();
529        let mut chunk_map_timings =
530            self.chunk_map
531                .tick_game(self, tick_count, random_tick_speed, runs_normally);
532        chunk_map_timings.lookup_cache.merge(early_lookup_stats);
533        let lookup_cache_scope = GameplayChunkLookupCacheScope::enter(&self.chunk_map);
534
535        if runs_normally {
536            let _span = tracing::trace_span!("block_events").entered();
537            self.run_block_events();
538        }
539
540        // Vanilla clears this before ticking entities and block entities.
541        self.handling_tick.store(false, Ordering::Relaxed);
542
543        let entity_tick = {
544            let _span = tracing::trace_span!("entity_tick").entered();
545            let start = Instant::now();
546            let dirty_chunks = self
547                .entity_manager
548                .tick_entities(tick_count as i32, runs_normally);
549            for chunk in dirty_chunks {
550                self.mark_chunk_dirty(chunk);
551            }
552            start.elapsed()
553        };
554
555        {
556            let _span = tracing::trace_span!("block_entities").entered();
557            let start = Instant::now();
558            self.block_entity_tickers.tick(self, runs_normally);
559            chunk_map_timings.tick_block_entities = start.elapsed();
560        }
561
562        {
563            let _span = tracing::trace_span!("entity_tracker_send_changes").entered();
564            self.entity_tracker.send_changes(
565                |chunk| self.get_packet_tracking_players(chunk),
566                |player_id| self.players.get_by_entity_id(player_id),
567                EntityChangeSenders {
568                    movement: |entity_id, packet| {
569                        self.broadcast_movement_sync_to_entity_trackers(entity_id, packet, None);
570                    },
571                    self_movement: |player_id, packet| {
572                        let Some(encoded) = self.encode_movement_sync_packet(packet) else {
573                            return;
574                        };
575                        let Some(player) = self.players.get_by_entity_id(player_id) else {
576                            return;
577                        };
578                        player.connection.send_encoded(encoded);
579                    },
580                    entity_data: |entity_id, dirty_entity_data| {
581                        let packet = CSetEntityData::new(entity_id, dirty_entity_data);
582                        let Ok(encoded) = EncodedPacket::from_bare(
583                            packet,
584                            self.compression,
585                            ConnectionProtocol::Play,
586                        ) else {
587                            return;
588                        };
589                        self.broadcast_to_entity_trackers_encoded(entity_id, encoded.clone(), None);
590                        if let Some(player) = self.players.get_by_entity_id(entity_id) {
591                            player.connection.send_encoded(encoded);
592                        }
593                    },
594                    attributes: |entity_id, dirty_attributes| {
595                        let packet = CUpdateAttributes::new(entity_id, dirty_attributes);
596                        let Ok(encoded) = EncodedPacket::from_bare(
597                            packet,
598                            self.compression,
599                            ConnectionProtocol::Play,
600                        ) else {
601                            return;
602                        };
603                        self.broadcast_to_entity_trackers_encoded(entity_id, encoded.clone(), None);
604                        if let Some(player) = self.players.get_by_entity_id(entity_id) {
605                            player.connection.send_encoded(encoded);
606                        }
607                    },
608                    mob_effects: |player_id, packet| {
609                        let Some(player) = self.players.get_by_entity_id(player_id) else {
610                            return;
611                        };
612                        match packet {
613                            MobEffectSyncPacket::Update(packet) => player.send_packet(packet),
614                            MobEffectSyncPacket::Remove(packet) => player.send_packet(packet),
615                        }
616                    },
617                    equipment: |entity_id, packet: CSetEquipment| {
618                        let Ok(encoded) = EncodedPacket::from_bare(
619                            packet,
620                            self.compression,
621                            ConnectionProtocol::Play,
622                        ) else {
623                            return;
624                        };
625                        self.broadcast_to_entity_trackers_encoded(entity_id, encoded, None);
626                    },
627                    passengers: |player_id, packet| {
628                        if let Some(player) = self.players.get_by_entity_id(player_id) {
629                            player.send_packet(packet);
630                        }
631                    },
632                    entity_link: |entity_id, packet: CSetEntityLink| {
633                        let Ok(encoded) = EncodedPacket::from_bare(
634                            packet,
635                            self.compression,
636                            ConnectionProtocol::Play,
637                        ) else {
638                            return;
639                        };
640                        self.broadcast_to_entity_trackers_encoded(entity_id, encoded, None);
641                    },
642                },
643            );
644        }
645
646        chunk_map_timings
647            .lookup_cache
648            .merge(lookup_cache_scope.finish());
649        WorldGameTickTimings {
650            elapsed: world_start.elapsed(),
651            chunk_map: chunk_map_timings,
652            entity_tick,
653        }
654    }
655
656    /// Saves all dirty chunks in this world to disk.
657    ///
658    /// This should be called during graceful shutdown.
659    /// Returns the number of chunks saved.
660    pub async fn save_all_chunks(&self) -> io::Result<usize> {
661        self.chunk_map.save_all_chunks().await
662    }
663}
664
665impl LevelReader for World {
666    fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
667        Self::get_block_state(self, pos)
668    }
669
670    fn get_block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
671        Self::get_block_entity(self, pos)
672    }
673
674    fn is_face_sturdy_for(
675        &self,
676        state: BlockStateId,
677        pos: BlockPos,
678        direction: Direction,
679        support_type: SupportType,
680    ) -> bool {
681        BLOCK_BEHAVIORS
682            .get_behavior(state.get_block())
683            .is_face_sturdy(state, self, pos, direction, support_type)
684    }
685
686    fn raw_brightness(&self, pos: BlockPos, sky_darkening: u8) -> u8 {
687        let sky_light = if self.dimension_type.has_skylight {
688            self.light_value_at(LightLayer::Sky, pos)
689                .saturating_sub(sky_darkening)
690        } else {
691            0
692        };
693
694        if sky_light == MAX_LIGHT_LEVEL {
695            return MAX_LIGHT_LEVEL;
696        }
697
698        sky_light.max(self.light_value_at(LightLayer::Block, pos))
699    }
700
701    fn can_see_sky(&self, pos: BlockPos) -> bool {
702        Self::can_see_sky(self, pos)
703    }
704
705    fn ambient_light(&self) -> f32 {
706        self.dimension_type.ambient_light
707    }
708
709    fn min_y(&self) -> i32 {
710        self.get_min_y()
711    }
712
713    fn height(&self) -> i32 {
714        self.get_height()
715    }
716}
717
718impl LevelReader for Arc<World> {
719    fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
720        self.as_ref().get_block_state(pos)
721    }
722
723    fn get_block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
724        self.as_ref().get_block_entity(pos)
725    }
726
727    fn is_face_sturdy_for(
728        &self,
729        state: BlockStateId,
730        pos: BlockPos,
731        direction: Direction,
732        support_type: SupportType,
733    ) -> bool {
734        self.as_ref()
735            .is_face_sturdy_for(state, pos, direction, support_type)
736    }
737
738    fn raw_brightness(&self, pos: BlockPos, sky_darkening: u8) -> u8 {
739        self.as_ref().raw_brightness(pos, sky_darkening)
740    }
741
742    fn can_see_sky(&self, pos: BlockPos) -> bool {
743        self.as_ref().can_see_sky(pos)
744    }
745
746    fn ambient_light(&self) -> f32 {
747        self.as_ref().ambient_light()
748    }
749
750    fn height_at(&self, heightmap_type: HeightmapType, x: i32, z: i32) -> i32 {
751        let mapped_type = match heightmap_type {
752            HeightmapType::WorldSurfaceWg => HeightmapType::WorldSurface,
753            HeightmapType::OceanFloorWg => HeightmapType::OceanFloor,
754            other => other,
755        };
756        self.as_ref()
757            .height_at(mapped_type, x, z)
758            .unwrap_or_else(|| self.min_y())
759    }
760
761    fn min_y(&self) -> i32 {
762        self.as_ref().get_min_y()
763    }
764
765    fn height(&self) -> i32 {
766        self.as_ref().get_height()
767    }
768}
769
770impl ScheduledTickAccess for Arc<World> {
771    fn fluid_tick_delay(&self, fluid: FluidRef) -> i32 {
772        FLUID_BEHAVIORS.get_behavior(fluid).tick_delay(self)
773    }
774
775    fn schedule_block_tick_default(&self, pos: BlockPos, block: BlockRef, delay: i32) -> bool {
776        self.as_ref().schedule_block_tick_default(pos, block, delay);
777        true
778    }
779
780    fn has_scheduled_block_tick(&self, pos: BlockPos, block: BlockRef) -> bool {
781        self.as_ref().has_scheduled_block_tick(pos, block)
782    }
783
784    fn will_tick_block_this_tick(&self, pos: BlockPos, block: BlockRef) -> bool {
785        self.as_ref().will_tick_block_this_tick(pos, block)
786    }
787
788    fn schedule_fluid_tick_default(&self, pos: BlockPos, fluid: FluidRef, delay: i32) -> bool {
789        self.as_ref().schedule_fluid_tick_default(pos, fluid, delay);
790        true
791    }
792
793    fn will_tick_fluid_this_tick(&self, pos: BlockPos, fluid: FluidRef) -> bool {
794        self.as_ref().will_tick_fluid_this_tick(pos, fluid)
795    }
796}
797
798impl LevelAccessor for Arc<World> {
799    fn set_block_state(&self, pos: BlockPos, state: BlockStateId, flags: UpdateFlags) -> bool {
800        self.set_block(pos, state, flags)
801    }
802
803    fn can_write_to_chunk(&self, chunk_x: i32, chunk_z: i32) -> bool {
804        self.chunk_map
805            .with_full_chunk(ChunkPos::new(chunk_x, chunk_z), |_| ())
806            .is_some()
807    }
808
809    fn requires_live_write_preflight(&self) -> bool {
810        true
811    }
812
813    fn destroy_block(&self, pos: BlockPos, drop_items: bool) -> bool {
814        World::destroy_block(self, pos, drop_items)
815    }
816
817    fn play_block_sound(
818        &self,
819        sound: SoundEventRef,
820        pos: BlockPos,
821        volume: f32,
822        pitch: f32,
823        exclude: Option<i32>,
824    ) {
825        self.as_ref()
826            .play_block_sound(sound, pos, volume, pitch, exclude);
827    }
828
829    fn game_event(&self, event: GameEventRef, pos: BlockPos, context: &GameEventContext<'_>) {
830        World::game_event(self, event, pos, context);
831    }
832}