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_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;
117pub 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#[derive(Debug)]
181pub struct WorldGameTickTimings {
182 pub elapsed: Duration,
184 pub chunk_map: ChunkMapGameTickTimings,
186 pub entity_tick: Duration,
188}
189
190#[must_use]
192#[derive(Clone, Copy, Debug, Eq, PartialEq)]
193pub enum ConditionalBlockSetResult {
194 Changed,
196 Unchanged,
198 Stale(BlockStateId),
200 Unavailable,
202}
203
204#[derive(Clone)]
206pub struct WorldConfig {
207 pub game_time_source: GameTimeSource,
209 pub storage: WorldStorageConfig,
211 pub level_data_path: Option<String>,
213 pub generator: Arc<ChunkGeneratorType>,
215 pub generation_settings: WorldGenerationSettings,
217 pub view_distance: u8,
219 pub simulation_distance: u8,
221 pub max_chained_neighbor_updates: i32,
223 pub compression: Option<CompressionInfo>,
225 pub is_flat: bool,
227 pub sea_level: i32,
229 pub default_gamemode: GameType,
231 pub difficulty: Difficulty,
233}
234
235pub struct World {
237 pub chunk_map: Arc<ChunkMap>,
239 pub players: PlayerMap,
241 pub player_area_map: PlayerAreaMap,
243 pub key: Identifier,
245 pub dimension_type: DimensionTypeRef,
251 pub level_data: SyncRwLock<LevelDataManager>,
253 pub(crate) game_time: Arc<GameTime>,
254 pub(crate) saved_data: SavedDataManager,
256 world_border: SyncMutex<WorldBorder>,
258 sleep_status: SyncMutex<sleep_status::SleepStatus>,
260 pub view_distance: u8,
262 pub simulation_distance: u8,
264 pub compression: Option<CompressionInfo>,
266 pub is_flat: bool,
268 pub sea_level: i32,
270 pub default_gamemode: GameType,
272 tick_runs_normally: AtomicBool,
275 handling_tick: AtomicBool,
277 block_events: SyncMutex<BlockEventQueue>,
279 neighbor_updater: CollectingNeighborUpdater,
281 entity_manager: WorldEntityManager,
283 block_entity_tickers: block_entity_ticker::WorldBlockEntityTickers,
285 game_event_listener_count: Arc<GameEventListenerCount>,
287 entity_tracker: EntityTracker,
289 navigating_mobs: NavigatingMobTracker,
291 pub weather: SyncMutex<Weather>,
293 redstone_torch_toggles: SyncMutex<redstone::RedstoneTorchToggleTracker>,
295 scheduled_ticks: tick_scheduler::WorldTickScheduler,
297 scheduled_block_ticks_this_tick:
299 SyncMutex<Option<Arc<tick_scheduler::ScheduledTickRunBatch<BlockRef>>>>,
300 scheduled_fluid_ticks_this_tick:
302 SyncMutex<Option<Arc<tick_scheduler::ScheduledTickRunBatch<FluidRef>>>>,
303 pub poi_storage: SyncMutex<PointOfInterestStorage>,
305 pending_world_changes: SyncMutex<Vec<(SharedEntity, WorldChangeRequest)>>,
307}
308
309impl World {
310 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 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 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 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 #[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 #[must_use]
493 pub fn domain(&self) -> &str {
494 self.key.namespace.as_ref()
495 }
496
497 #[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 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 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}