1use std::{
4 io, mem,
5 path::Path,
6 sync::{
7 Arc, LazyLock, Weak,
8 atomic::{AtomicBool, AtomicI8, Ordering},
9 },
10 time::Duration,
11};
12
13use crate::chunk::chunk_ticket_storage::ChunkTicketStorage;
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::entity::damage::DamageHistory;
21use crate::poi::OccupationStatus;
22use crate::portal::WorldChangeRequest;
23use crate::world::game_event::{
24 GameEventContext, GameEventDispatcher, GameEventListenerCount, GameEventListenerStorage,
25 SharedGameEventListener,
26};
27use crate::{chunk::chunk_map::ChunkMapGameTickTimings, world::weather::Weather};
28use steel_utils::saved_data::{SavedDataManager, names as saved_data_names};
29
30use glam::DVec3;
31use sha2::{Digest, Sha256};
32use steel_protocol::packets::game::{
33 CBlockDestruction, CChangeDifficulty, CGameEvent, CInitializeBorder, CLevelEvent,
34 CLevelParticles, CPlayerChat, CSetBorderCenter, CSetBorderLerpSize, CSetBorderSize,
35 CSetBorderWarningDelay, CSetBorderWarningDistance, CSetEntityData, CSetEntityLink,
36 CSetEquipment, CSound, CSystemChat, CUpdateAttributes, GameEventType, RandomizationType,
37 SoundSource,
38};
39use steel_protocol::utils::ConnectionProtocol;
40use steel_protocol::{
41 packet_traits::{ClientPacket, CompressionInfo, EncodedPacket},
42 packets::game::CSetTime,
43};
44
45use rustc_hash::FxHashSet;
46use simdnbt::owned::NbtCompound;
47use steel_registry::biome::{BiomeRef, TemperatureModifier};
48use steel_registry::blocks::block_state_ext::BlockStateExt;
49use steel_registry::blocks::properties::{Axis, BlockStateProperties, Direction};
50use steel_registry::blocks::shapes::{
51 BooleanOp, OffsetVoxelShape, SupportType, VoxelShape, is_offset_face_full, is_shape_full_block,
52 join_is_not_empty,
53};
54use steel_registry::fluid::{FluidRef, FluidState};
55use steel_registry::game_events::GameEventRef;
56use steel_registry::game_rules::{ErasedGameRuleRef, GameRule, GameRuleValue, GameRuleValueType};
57use steel_registry::item_stack::ItemStack;
58use steel_registry::level_events;
59use steel_registry::loot_table::LootContext;
60use steel_registry::particle_type::ParticleData;
61use steel_registry::sound_event::SoundEventRef;
62use steel_registry::vanilla_block_tags::BlockTag;
63use steel_registry::vanilla_game_rules::{
64 BLOCK_DROPS, GLOBAL_SOUND_EVENTS, PLAYERS_NETHER_PORTAL_DEFAULT_DELAY, RANDOM_TICK_SPEED,
65};
66use steel_registry::{REGISTRY, RegistryEntry, RegistryExt, dimension_type::DimensionTypeRef};
67use steel_registry::{block_entity_type::BlockEntityTypeRef, vanilla_dimension_types};
68use steel_registry::{
69 blocks::BlockRef, vanilla_game_rules::ADVANCE_TIME, vanilla_game_rules::ADVANCE_WEATHER,
70};
71use steel_registry::{vanilla_blocks, vanilla_entities, vanilla_game_events, vanilla_poi_types};
72use steel_utils::block_util::FoundRectangle;
73use steel_utils::{
74 Downcast as _,
75 locks::{SyncMutex, SyncRwLock},
76 random::{Random as _, RandomSource, legacy_random::LegacyRandom},
77};
78use steel_worldgen::{biomes::obfuscate_biome_seed, noise::PerlinSimplexNoise};
79
80use steel_utils::{
81 BlockLocalAabb, BlockPos, BlockStateId, ChunkPos, Identifier, PackedBlockPos, SectionPos,
82 WorldAabb,
83 types::{Difficulty, GameType, UpdateFlags},
84};
85use tokio::{runtime::Runtime, time::Instant};
86
87use crate::{
88 ChunkMap,
89 behavior::{BLOCK_BEHAVIORS, BlockCollisionContext, BlockLootContext, FLUID_BEHAVIORS},
90 block_entity::{BlockEntity, SharedBlockEntity, entities::EndGatewayBlockEntity},
91 chunk::{heightmap::HeightmapType, player_chunk_view::PlayerChunkView},
92 chunk_saver::{ChunkStorage, RamOnlyStorage, RegionManager},
93 entity::{
94 AddEntityError, Entity, EntityChangeSenders, EntityChunkCallback, EntityLifecycleChanges,
95 EntityMovementSyncPacket, EntityOwnership, EntityTracker, EntityVisibility,
96 InactiveEntityCallback, MobEffectSyncPacket, RemovalReason, SharedEntity,
97 WorldEntityManager,
98 entities::{ExperienceOrbEntity, ItemEntity},
99 entity_loot_ref,
100 },
101 fluid::{FluidStateExt as _, fluid_state_to_block},
102 level_data::{
103 GameTime, GameTimeSource, LevelDataManager, RespawnData, WorldGenerationSettings,
104 },
105 player::{LastSeen, Player, connection::NetworkConnection},
106 poi::PointOfInterestStorage,
107};
108
109mod block_entity_ticker;
110mod block_event;
111mod block_region;
112mod block_updates;
113mod border;
114mod broadcasts;
115pub(crate) mod clock;
116mod domain_entity_directory;
117mod entity_management;
118mod environment;
119mod events;
120pub mod explosion;
121pub mod game_event;
123mod level_effects;
124mod level_reader;
125mod player_index;
126pub(crate) mod player_spawn_finder;
127mod portals;
128mod properties;
129mod raycast;
130mod redstone;
131mod signal_getter;
132mod sleep;
133mod sleep_status;
134mod spawn;
135pub mod tick_scheduler;
136mod weather;
137mod world_entities;
138
139#[cfg(test)]
140mod tests;
141
142pub use crate::config::WorldStorageConfig;
143use crate::worldgen::generators::vanilla::fuzzed_biome_at_block;
144use crate::worldgen::{ChunkGenerator, ChunkGeneratorType};
145use block_event::BlockEventQueue;
146pub(crate) use block_region::{BlockRegionBounds, BlockRegionRead, MAX_BLOCK_REGION_WORKSET_SLOTS};
147use block_updates::CollectingNeighborUpdater;
148pub use border::WorldBorderError;
149pub(crate) use border::{MAX_CENTER_COORDINATE, MAX_SIZE};
150use border::{WorldBorder, WorldBorderSnapshot};
151pub(crate) use domain_entity_directory::DomainEntityDirectory;
152use entity_management::NavigatingMobTracker;
153#[cfg(test)]
154use entity_management::nearest_player_distance_in_range;
155pub use explosion::{
156 BlockInteraction, DefaultExplosionDamageCalculator, EntityBasedExplosionDamageCalculator,
157 Explosion, ExplosionDamageCalculator, ExplosionInteraction, ExplosionOptions, ExplosionOutcome,
158};
159pub(crate) use explosion::{ExplosionBlockReader, ImmutableExplosionBlockCalculator};
160pub use level_reader::{LevelAccessor, LevelReader, ScheduledTickAccess};
161pub use player_index::{PlayerAreaMap, PlayerMap};
162pub use raycast::{ClipBlockShape, ClipFluid, ClipHitResult, RaytraceAction};
163#[cfg(test)]
164pub(crate) use signal_getter::get_best_neighbor_signal;
165pub use signal_getter::{SignalGetter, SignalQueryContext};
166pub(crate) use signal_getter::{get_control_input_signal, get_signal, is_redstone_conductor};
167pub use tick_scheduler::ScheduledTick;
168
169#[cfg(test)]
170use level_effects::sound_is_within_range;
171#[cfg(test)]
172use portals::{
173 closest_portal_candidate, nether_portal_creation_scan_origin, nether_portal_frame_offset_pos,
174};
175
176const fn initialize_border_packet(snapshot: WorldBorderSnapshot) -> CInitializeBorder {
177 CInitializeBorder {
178 new_center_x: snapshot.center_x,
179 new_center_z: snapshot.center_z,
180 old_size: snapshot.old_size,
181 new_size: snapshot.new_size,
182 lerp_time: snapshot.lerp_time,
183 new_absolute_max_size: snapshot.absolute_max_size,
184 warning_blocks: snapshot.warning_blocks,
185 warning_time: snapshot.warning_time,
186 }
187}
188
189#[derive(Debug)]
191pub struct WorldGameTickTimings {
192 pub elapsed: Duration,
194 pub chunk_map: ChunkMapGameTickTimings,
196 pub entity_tick: Duration,
198}
199
200#[must_use]
202#[derive(Clone, Copy, Debug, Eq, PartialEq)]
203pub enum ConditionalBlockSetResult {
204 Changed,
206 Unchanged,
208 Stale(BlockStateId),
210 Unavailable,
212}
213
214#[derive(Clone)]
216pub struct WorldConfig {
217 pub damage_history: Arc<DamageHistory>,
219 pub game_time_source: GameTimeSource,
221 pub storage: WorldStorageConfig,
223 pub level_data_path: Option<String>,
225 pub generator: Arc<ChunkGeneratorType>,
227 pub generation_settings: WorldGenerationSettings,
229 pub view_distance: u8,
231 pub simulation_distance: u8,
233 pub max_chained_neighbor_updates: i32,
235 pub compression: Option<CompressionInfo>,
237 pub is_flat: bool,
239 pub sea_level: i32,
241 pub default_gamemode: GameType,
243 pub difficulty: Difficulty,
245}
246
247pub struct World {
249 pub(crate) damage_history: Weak<DamageHistory>,
250 pub chunk_map: Arc<ChunkMap>,
252 pub players: PlayerMap,
254 pub player_area_map: PlayerAreaMap,
256 pub key: Identifier,
258 pub dimension_type: DimensionTypeRef,
264 pub level_data: SyncRwLock<LevelDataManager>,
266 pub(crate) game_time: Arc<GameTime>,
267 pub(crate) saved_data: SavedDataManager,
269 world_border: SyncMutex<WorldBorder>,
271 sleep_status: SyncMutex<sleep_status::SleepStatus>,
273 pub view_distance: u8,
275 pub simulation_distance: u8,
277 pub compression: Option<CompressionInfo>,
279 pub is_flat: bool,
281 pub sea_level: i32,
283 default_gamemode: AtomicI8,
285 tick_runs_normally: AtomicBool,
288 handling_tick: AtomicBool,
290 block_events: SyncMutex<BlockEventQueue>,
292 neighbor_updater: CollectingNeighborUpdater,
294 entity_manager: WorldEntityManager,
296 domain_entity_directory: SyncRwLock<Option<Arc<DomainEntityDirectory>>>,
297 block_entity_tickers: block_entity_ticker::WorldBlockEntityTickers,
299 game_event_listener_count: Arc<GameEventListenerCount>,
301 entity_tracker: EntityTracker,
303 navigating_mobs: NavigatingMobTracker,
305 pub weather: SyncMutex<Weather>,
307 redstone_torch_toggles: SyncMutex<redstone::RedstoneTorchToggleTracker>,
309 scheduled_ticks: tick_scheduler::WorldTickScheduler,
311 scheduled_block_ticks_this_tick:
313 SyncMutex<Option<Arc<tick_scheduler::ScheduledTickRunBatch<BlockRef>>>>,
314 scheduled_fluid_ticks_this_tick:
316 SyncMutex<Option<Arc<tick_scheduler::ScheduledTickRunBatch<FluidRef>>>>,
317 pub poi_storage: SyncMutex<PointOfInterestStorage>,
319 pending_world_changes: SyncMutex<Vec<(SharedEntity, WorldChangeRequest)>>,
321}
322
323impl World {
324 pub async fn new_with_config(
336 chunk_runtime: Arc<Runtime>,
337 key: Identifier,
338 dimension_type: DimensionTypeRef,
339 seed: i64,
340 config: WorldConfig,
341 generation_pool: Arc<rayon::ThreadPool>,
342 ) -> io::Result<Arc<Self>> {
343 let chunk_encoding_pool = Arc::clone(&generation_pool);
344 Self::new_with_config_and_encoding_pool(
345 chunk_runtime,
346 key,
347 dimension_type,
348 seed,
349 config,
350 generation_pool,
351 chunk_encoding_pool,
352 )
353 .await
354 }
355
356 pub(crate) async fn new_with_config_and_encoding_pool(
357 chunk_runtime: Arc<Runtime>,
358 key: Identifier,
359 dimension_type: DimensionTypeRef,
360 seed: i64,
361 config: WorldConfig,
362 generation_pool: Arc<rayon::ThreadPool>,
363 chunk_encoding_pool: Arc<rayon::ThreadPool>,
364 ) -> io::Result<Arc<Self>> {
365 let view_distance = config.view_distance;
366 let simulation_distance = config.simulation_distance;
367 let max_chained_neighbor_updates = config.max_chained_neighbor_updates;
368 let compression = config.compression;
369 let is_flat = config.is_flat;
370 let sea_level = config.sea_level;
371 let default_gamemode = config.default_gamemode;
372 let storage: Arc<ChunkStorage> = match &config.storage {
374 WorldStorageConfig::Disk { path } => {
375 Arc::new(ChunkStorage::Disk(RegionManager::new(path.clone())))
376 }
377 WorldStorageConfig::RamOnly => {
378 Arc::new(ChunkStorage::RamOnly(RamOnlyStorage::empty_world()))
379 }
380 };
381
382 let path = config.level_data_path.as_deref().map(Path::new);
385 let saved_data = SavedDataManager::new(path);
386 let mut level_data = LevelDataManager::new(
387 path,
388 seed,
389 config.difficulty,
390 config.generation_settings,
391 config.game_time_source,
392 )
393 .await?;
394 if level_data.is_dirty() {
395 level_data.save().await?;
396 }
397 let ticket_storage = ChunkTicketStorage::load(&saved_data, &key).await;
398 let world_border = WorldBorder::new(level_data.data().world_border)
399 .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
400 let mut weather = Weather::default();
411 if level_data.is_raining() {
412 weather.rain_level = 1.0;
413 if level_data.is_thundering() {
414 weather.thunder_level = 1.0;
415 }
416 }
417
418 Ok(Arc::new_cyclic(|weak_self: &Weak<World>| {
419 let chunk_map = Arc::new(ChunkMap::new_with_storage_and_ticket_storage(
420 chunk_runtime,
421 Weak::clone(weak_self),
422 dimension_type,
423 sea_level,
424 storage,
425 config.generator,
426 generation_pool,
427 chunk_encoding_pool,
428 view_distance,
429 simulation_distance,
430 ticket_storage,
431 ));
432 chunk_map.start_generation_refill_loop();
433
434 Self {
435 damage_history: Arc::downgrade(&config.damage_history),
436 chunk_map,
437 players: PlayerMap::new(),
438 player_area_map: PlayerAreaMap::new(),
439 key,
440 dimension_type,
441 game_time: level_data.game_time_handle(),
442 level_data: SyncRwLock::new(level_data),
443 saved_data,
444 world_border: SyncMutex::new(world_border),
445 sleep_status: SyncMutex::new(sleep_status::SleepStatus::default()),
446 view_distance,
447 simulation_distance,
448 compression,
449 is_flat,
450 sea_level,
451 default_gamemode: AtomicI8::new(default_gamemode.into()),
452 tick_runs_normally: AtomicBool::new(true),
453 handling_tick: AtomicBool::new(false),
454 block_events: SyncMutex::new(BlockEventQueue::default()),
455 neighbor_updater: CollectingNeighborUpdater::new(max_chained_neighbor_updates),
456 entity_manager: WorldEntityManager::new(),
457 domain_entity_directory: SyncRwLock::new(None),
458 block_entity_tickers: block_entity_ticker::WorldBlockEntityTickers::new(),
459 game_event_listener_count: GameEventListenerCount::shared(),
460 entity_tracker: EntityTracker::new(),
461 navigating_mobs: NavigatingMobTracker::new(),
462 weather: SyncMutex::new(weather),
463 redstone_torch_toggles: SyncMutex::new(
464 redstone::RedstoneTorchToggleTracker::default(),
465 ),
466 scheduled_ticks: tick_scheduler::WorldTickScheduler::new(),
467 scheduled_block_ticks_this_tick: SyncMutex::new(None),
468 scheduled_fluid_ticks_this_tick: SyncMutex::new(None),
469 poi_storage: SyncMutex::new(PointOfInterestStorage::new()),
470 pending_world_changes: SyncMutex::new(Vec::new()),
471 }
472 }))
473 }
474
475 #[expect(
477 clippy::await_holding_lock,
478 reason = "holding the write lock across await is safe here because it only happens during shutdown"
479 )]
480 pub async fn cleanup(&self, total_saved: &mut usize) {
481 self.sync_world_border_to_level_data();
482 match self.level_data.write().save().await {
483 Ok(()) => log::info!("World {} level data saved successfully", self.key),
484 Err(e) => log::error!("Failed to save world level data: {e}"),
485 }
486
487 let chunk_tickets = self.chunk_map.persistent_chunk_tickets();
488 match self
489 .saved_data
490 .save(saved_data_names::CHUNK_TICKETS, &chunk_tickets)
491 .await
492 {
493 Ok(()) => log::info!("World {} saved chunk ticket data successfully", self.key),
494 Err(e) => log::error!("Failed to save world chunk ticket data: {e}"),
495 }
496
497 match self.save_all_chunks().await {
498 Ok(count) => *total_saved += count,
499 Err(e) => log::error!("Failed to save world chunks: {e}"),
500 }
501 }
502
503 #[must_use]
505 pub fn domain(&self) -> &str {
506 self.key.namespace.as_ref()
507 }
508
509 pub(crate) fn set_domain_entity_directory(&self, directory: Arc<DomainEntityDirectory>) {
510 *self.domain_entity_directory.write() = Some(directory);
511 }
512
513 #[must_use]
515 pub fn get_entity_in_domain_by_uuid(&self, uuid: &uuid::Uuid) -> Option<SharedEntity> {
516 self.get_entity_by_uuid(uuid).or_else(|| {
517 self.domain_entity_directory
518 .read()
519 .as_ref()
520 .and_then(|directory| directory.get_entity_by_uuid(uuid))
521 })
522 }
523
524 fn tick_removed_players(&self) {
530 self.players.iter_players(|_, player| {
531 if player.is_removed() {
532 Arc::clone(player).tick();
533 }
534 true
535 });
536 }
537
538 #[tracing::instrument(level = "trace", skip(self), name = "world_game_tick")]
545 #[expect(
546 clippy::too_many_lines,
547 reason = "world tick orchestration keeps vanilla subsystem order explicit"
548 )]
549 pub fn tick_game(
550 self: &Arc<Self>,
551 tick_count: u64,
552 runs_normally: bool,
553 ) -> WorldGameTickTimings {
554 let world_start = Instant::now();
555 let lookup_cache_scope = GameplayChunkLookupCacheScope::enter(&self.chunk_map);
556 self.handling_tick.store(true, Ordering::Relaxed);
557 self.set_tick_runs_normally(runs_normally);
558 if runs_normally {
559 self.tick_world_border();
560 self.tick_weather();
561 }
562 self.tick_sleeping_players();
563 if runs_normally {
564 self.tick_time();
565 }
566
567 let random_tick_speed = self.get_game_rule(&RANDOM_TICK_SPEED) as u32;
568
569 let early_lookup_stats = lookup_cache_scope.finish();
570 let mut chunk_map_timings =
571 self.chunk_map
572 .tick_game(self, tick_count, random_tick_speed, runs_normally);
573 chunk_map_timings.lookup_cache.merge(early_lookup_stats);
574 let lookup_cache_scope = GameplayChunkLookupCacheScope::enter(&self.chunk_map);
575
576 if runs_normally {
577 let _span = tracing::trace_span!("block_events").entered();
578 self.run_block_events();
579 }
580
581 self.handling_tick.store(false, Ordering::Relaxed);
583
584 self.tick_removed_players();
585
586 let entity_tick = {
587 let _span = tracing::trace_span!("entity_tick").entered();
588 let start = Instant::now();
589 let dirty_chunks = self
590 .entity_manager
591 .tick_entities(tick_count as i32, runs_normally);
592 for chunk in dirty_chunks {
593 self.mark_chunk_dirty(chunk);
594 }
595 start.elapsed()
596 };
597
598 {
599 let _span = tracing::trace_span!("block_entities").entered();
600 let start = Instant::now();
601 self.block_entity_tickers.tick(self, runs_normally);
602 chunk_map_timings.tick_block_entities = start.elapsed();
603 }
604
605 {
606 let _span = tracing::trace_span!("entity_tracker_send_changes").entered();
607 self.entity_tracker.send_changes(
608 |chunk| self.get_packet_tracking_players(chunk),
609 |player_id| self.players.get_by_entity_id(player_id),
610 EntityChangeSenders {
611 movement: |entity_id, packet| {
612 self.broadcast_movement_sync_to_entity_trackers(entity_id, packet, None);
613 },
614 self_movement: |player_id, packet| {
615 let Some(encoded) = self.encode_movement_sync_packet(packet) else {
616 return;
617 };
618 let Some(player) = self.players.get_by_entity_id(player_id) else {
619 return;
620 };
621 player.connection.send_encoded(encoded);
622 },
623 entity_data: |entity_id, dirty_entity_data| {
624 let packet = CSetEntityData::new(entity_id, dirty_entity_data);
625 let Ok(encoded) = EncodedPacket::from_bare(
626 packet,
627 self.compression,
628 ConnectionProtocol::Play,
629 ) else {
630 return;
631 };
632 self.broadcast_to_entity_trackers_encoded(entity_id, encoded.clone(), None);
633 if let Some(player) = self.players.get_by_entity_id(entity_id) {
634 player.connection.send_encoded(encoded);
635 }
636 },
637 attributes: |entity_id, dirty_attributes| {
638 let packet = CUpdateAttributes::new(entity_id, dirty_attributes);
639 let Ok(encoded) = EncodedPacket::from_bare(
640 packet,
641 self.compression,
642 ConnectionProtocol::Play,
643 ) else {
644 return;
645 };
646 self.broadcast_to_entity_trackers_encoded(entity_id, encoded.clone(), None);
647 if let Some(player) = self.players.get_by_entity_id(entity_id) {
648 player.connection.send_encoded(encoded);
649 }
650 },
651 mob_effects: |player_id, packet| {
652 let Some(player) = self.players.get_by_entity_id(player_id) else {
653 return;
654 };
655 match packet {
656 MobEffectSyncPacket::Update(packet) => player.send_packet(packet),
657 MobEffectSyncPacket::Remove(packet) => player.send_packet(packet),
658 }
659 },
660 equipment: |entity_id, packet: CSetEquipment| {
661 let Ok(encoded) = EncodedPacket::from_bare(
662 packet,
663 self.compression,
664 ConnectionProtocol::Play,
665 ) else {
666 return;
667 };
668 self.broadcast_to_entity_trackers_encoded(entity_id, encoded, None);
669 },
670 passengers: |player_id, packet| {
671 if let Some(player) = self.players.get_by_entity_id(player_id) {
672 player.send_packet(packet);
673 }
674 },
675 entity_link: |entity_id, packet: CSetEntityLink| {
676 let Ok(encoded) = EncodedPacket::from_bare(
677 packet,
678 self.compression,
679 ConnectionProtocol::Play,
680 ) else {
681 return;
682 };
683 self.broadcast_to_entity_trackers_encoded(entity_id, encoded, None);
684 },
685 },
686 );
687 }
688
689 chunk_map_timings
690 .lookup_cache
691 .merge(lookup_cache_scope.finish());
692 WorldGameTickTimings {
693 elapsed: world_start.elapsed(),
694 chunk_map: chunk_map_timings,
695 entity_tick,
696 }
697 }
698
699 pub async fn save_all_chunks(&self) -> io::Result<usize> {
704 self.chunk_map.save_all_chunks().await
705 }
706}
707
708impl LevelReader for World {
709 fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
710 Self::get_block_state(self, pos)
711 }
712
713 fn get_block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
714 Self::get_block_entity(self, pos)
715 }
716
717 fn is_face_sturdy_for(
718 &self,
719 state: BlockStateId,
720 pos: BlockPos,
721 direction: Direction,
722 support_type: SupportType,
723 ) -> bool {
724 BLOCK_BEHAVIORS
725 .get_behavior(state.get_block())
726 .is_face_sturdy(state, self, pos, direction, support_type)
727 }
728
729 fn sky_darkening(&self) -> u8 {
730 World::sky_darkening(self)
731 }
732
733 fn raw_brightness(&self, pos: BlockPos, sky_darkening: u8) -> u8 {
734 let sky_light = if self.dimension_type.has_skylight {
735 self.light_value_at(LightLayer::Sky, pos)
736 .saturating_sub(sky_darkening)
737 } else {
738 0
739 };
740
741 if sky_light == MAX_LIGHT_LEVEL {
742 return MAX_LIGHT_LEVEL;
743 }
744
745 sky_light.max(self.light_value_at(LightLayer::Block, pos))
746 }
747
748 fn can_see_sky(&self, pos: BlockPos) -> bool {
749 Self::can_see_sky(self, pos)
750 }
751
752 fn ambient_light(&self) -> f32 {
753 self.dimension_type.ambient_light
754 }
755
756 fn min_y(&self) -> i32 {
757 self.get_min_y()
758 }
759
760 fn height(&self) -> i32 {
761 self.get_height()
762 }
763
764 fn sea_level(&self) -> i32 {
765 self.sea_level
766 }
767}
768
769impl LevelReader for Arc<World> {
770 fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
771 self.as_ref().get_block_state(pos)
772 }
773
774 fn get_block_entity(&self, pos: BlockPos) -> Option<SharedBlockEntity> {
775 self.as_ref().get_block_entity(pos)
776 }
777
778 fn is_face_sturdy_for(
779 &self,
780 state: BlockStateId,
781 pos: BlockPos,
782 direction: Direction,
783 support_type: SupportType,
784 ) -> bool {
785 self.as_ref()
786 .is_face_sturdy_for(state, pos, direction, support_type)
787 }
788
789 fn sky_darkening(&self) -> u8 {
790 self.as_ref().sky_darkening()
791 }
792
793 fn raw_brightness(&self, pos: BlockPos, sky_darkening: u8) -> u8 {
794 self.as_ref().raw_brightness(pos, sky_darkening)
795 }
796
797 fn can_see_sky(&self, pos: BlockPos) -> bool {
798 self.as_ref().can_see_sky(pos)
799 }
800
801 fn ambient_light(&self) -> f32 {
802 self.as_ref().ambient_light()
803 }
804
805 fn height_at(&self, heightmap_type: HeightmapType, x: i32, z: i32) -> i32 {
806 let mapped_type = match heightmap_type {
807 HeightmapType::WorldSurfaceWg => HeightmapType::WorldSurface,
808 HeightmapType::OceanFloorWg => HeightmapType::OceanFloor,
809 other => other,
810 };
811 self.as_ref()
812 .height_at(mapped_type, x, z)
813 .unwrap_or_else(|| self.min_y())
814 }
815
816 fn min_y(&self) -> i32 {
817 self.as_ref().get_min_y()
818 }
819
820 fn height(&self) -> i32 {
821 self.as_ref().get_height()
822 }
823
824 fn sea_level(&self) -> i32 {
825 self.as_ref().sea_level
826 }
827}
828
829impl ScheduledTickAccess for Arc<World> {
830 fn fluid_tick_delay(&self, fluid: FluidRef) -> i32 {
831 FLUID_BEHAVIORS.get_behavior(fluid).tick_delay(self)
832 }
833
834 fn schedule_block_tick_default(&self, pos: BlockPos, block: BlockRef, delay: i32) -> bool {
835 self.as_ref().schedule_block_tick_default(pos, block, delay);
836 true
837 }
838
839 fn has_scheduled_block_tick(&self, pos: BlockPos, block: BlockRef) -> bool {
840 self.as_ref().has_scheduled_block_tick(pos, block)
841 }
842
843 fn will_tick_block_this_tick(&self, pos: BlockPos, block: BlockRef) -> bool {
844 self.as_ref().will_tick_block_this_tick(pos, block)
845 }
846
847 fn schedule_fluid_tick_default(&self, pos: BlockPos, fluid: FluidRef, delay: i32) -> bool {
848 self.as_ref().schedule_fluid_tick_default(pos, fluid, delay);
849 true
850 }
851
852 fn will_tick_fluid_this_tick(&self, pos: BlockPos, fluid: FluidRef) -> bool {
853 self.as_ref().will_tick_fluid_this_tick(pos, fluid)
854 }
855}
856
857impl LevelAccessor for Arc<World> {
858 fn set_block_state(&self, pos: BlockPos, state: BlockStateId, flags: UpdateFlags) -> bool {
859 self.set_block(pos, state, flags)
860 }
861
862 fn can_write_to_chunk(&self, chunk_x: i32, chunk_z: i32) -> bool {
863 self.chunk_map
864 .with_full_chunk(ChunkPos::new(chunk_x, chunk_z), |_| ())
865 .is_some()
866 }
867
868 fn requires_live_write_preflight(&self) -> bool {
869 true
870 }
871
872 fn destroy_block(&self, pos: BlockPos, drop_items: bool) -> bool {
873 World::destroy_block(self, pos, drop_items)
874 }
875
876 fn play_block_sound(
877 &self,
878 sound: SoundEventRef,
879 pos: BlockPos,
880 volume: f32,
881 pitch: f32,
882 exclude: Option<i32>,
883 ) {
884 self.as_ref()
885 .play_block_sound(sound, pos, volume, pitch, exclude);
886 }
887
888 fn game_event(&self, event: GameEventRef, pos: BlockPos, context: &GameEventContext<'_>) {
889 World::game_event(self, event, pos, context);
890 }
891}