steel_core/world/
properties.rs1use super::{
2 ADVANCE_TIME, BlockPos, CChangeDifficulty, ChunkPos, Difficulty, Digest, ErasedGameRuleRef,
3 GameRule, GameRuleValue, GameRuleValueType, LevelDataManager, OffsetVoxelShape, Ordering,
4 Player, REGISTRY, SectionPos, Sha256, World, vanilla_dimension_types,
5};
6
7impl World {
8 pub const MAX_LEVEL_SIZE: i32 = 30_000_000;
10 pub const MAX_ENTITY_SPAWN_Y: i32 = 20_000_000;
12 pub const MIN_ENTITY_SPAWN_Y: i32 = -20_000_000;
14
15 #[must_use]
20 pub fn is_end_dimension_type(&self) -> bool {
21 self.dimension_type == &vanilla_dimension_types::THE_END
22 }
23
24 pub fn difficulty(&self) -> Difficulty {
26 self.level_data.read().data().difficulty
27 }
28
29 pub(crate) fn set_difficulty(&self, difficulty: Difficulty) {
31 let locked = {
32 let mut level_data = self.level_data.write();
33 level_data.data_mut().difficulty = difficulty;
34 level_data.data().difficulty_locked
35 };
36 self.broadcast_to_all(CChangeDifficulty { difficulty, locked });
37 }
38
39 pub const fn get_height(&self) -> i32 {
41 self.dimension_type.height
42 }
43
44 pub const fn get_min_y(&self) -> i32 {
46 self.dimension_type.min_y
47 }
48
49 pub const fn get_max_y(&self) -> i32 {
51 self.get_min_y() + self.get_height() - 1
52 }
53
54 pub const fn is_outside_build_height(&self, block_y: i32) -> bool {
56 block_y < self.get_min_y() || block_y > self.get_max_y()
57 }
58
59 #[expect(clippy::unused_self, reason = "this is an api function")]
61 pub const fn is_in_valid_bounds_horizontal(&self, block_pos: BlockPos) -> bool {
62 let chunk_x = SectionPos::block_to_section_coord(block_pos.0.x);
63 let chunk_z = SectionPos::block_to_section_coord(block_pos.0.z);
64 ChunkPos::is_valid(chunk_x, chunk_z)
65 }
66
67 pub const fn is_in_valid_bounds(&self, block_pos: BlockPos) -> bool {
69 !self.is_outside_build_height(block_pos.0.y)
70 && self.is_in_valid_bounds_horizontal(block_pos)
71 }
72
73 pub const fn is_in_world_bounds(&self, block_pos: BlockPos) -> bool {
75 !self.is_outside_build_height(block_pos.0.y)
76 && Self::is_in_world_bounds_horizontal(block_pos)
77 }
78
79 #[must_use]
81 pub const fn is_in_spawnable_bounds(block_pos: BlockPos) -> bool {
82 !Self::is_outside_spawnable_height(block_pos.0.y)
83 && Self::is_in_world_bounds_horizontal(block_pos)
84 }
85
86 pub(super) const fn is_in_world_bounds_horizontal(block_pos: BlockPos) -> bool {
87 block_pos.0.x >= -Self::MAX_LEVEL_SIZE
88 && block_pos.0.z >= -Self::MAX_LEVEL_SIZE
89 && block_pos.0.x < Self::MAX_LEVEL_SIZE
90 && block_pos.0.z < Self::MAX_LEVEL_SIZE
91 }
92
93 pub(super) const fn is_outside_spawnable_height(y: i32) -> bool {
94 y < Self::MIN_ENTITY_SPAWN_Y || y >= Self::MAX_ENTITY_SPAWN_Y
95 }
96
97 #[must_use]
100 pub const fn max_build_height(&self) -> i32 {
101 self.get_min_y() + self.get_height()
102 }
103
104 #[must_use]
107 pub const fn may_interact(&self, _player: &Player, pos: BlockPos) -> bool {
108 self.is_in_valid_bounds(pos)
109 }
110
111 #[must_use]
118 pub fn is_unobstructed(&self, collision_shape: OffsetVoxelShape, pos: BlockPos) -> bool {
119 if collision_shape.is_empty() {
120 return true;
121 }
122
123 for block_aabb in collision_shape.iter() {
124 let world_aabb = block_aabb.at_block(pos);
125 for entity in self.get_entities_in_aabb(&world_aabb) {
126 if entity.blocks_building() && entity.bounding_box().intersects(world_aabb) {
127 return false;
128 }
129 }
130 }
131
132 true
133 }
134
135 #[must_use]
140 pub fn tick_runs_normally(&self) -> bool {
141 self.tick_runs_normally.load(Ordering::Relaxed)
142 }
143
144 pub fn set_tick_runs_normally(&self, runs_normally: bool) {
148 self.tick_runs_normally
149 .store(runs_normally, Ordering::Relaxed);
150 }
151
152 #[must_use]
154 pub(crate) fn is_handling_tick(&self) -> bool {
155 self.handling_tick.load(Ordering::Relaxed)
156 }
157
158 #[must_use]
162 pub fn get_game_rule<T: GameRuleValueType>(&self, rule: &GameRule<T>) -> T {
163 let guard = self.level_data.read();
164 self.get_game_rule_with_guard(rule, &guard)
165 }
166
167 #[expect(clippy::unused_self, reason = "this is an api function")]
169 #[must_use]
170 pub fn get_game_rule_with_guard<T: GameRuleValueType>(
171 &self,
172 rule: &GameRule<T>,
173 guard: &LevelDataManager,
174 ) -> T {
175 guard
176 .data()
177 .game_rules_values
178 .get(rule, ®ISTRY.game_rules)
179 }
180
181 #[must_use]
183 pub fn get_erased_game_rule(&self, rule: ErasedGameRuleRef) -> GameRuleValue {
184 self.level_data
185 .read()
186 .data()
187 .game_rules_values
188 .get_erased(rule, ®ISTRY.game_rules)
189 .clone()
190 }
191
192 pub fn set_game_rule<T: GameRuleValueType>(&self, rule: &GameRule<T>, value: T) -> bool {
196 let updated = {
197 let mut guard = self.level_data.write();
198 self.set_game_rule_with_guard(rule, value, &mut guard)
199 };
200 if updated && rule.key() == ADVANCE_TIME.key() {
201 self.broadcast_time_sync();
202 }
203 updated
204 }
205
206 #[expect(clippy::unused_self, reason = "this is an api function")]
208 pub fn set_game_rule_with_guard<T: GameRuleValueType>(
209 &self,
210 rule: &GameRule<T>,
211 value: T,
212 guard: &mut LevelDataManager,
213 ) -> bool {
214 guard
215 .data_mut()
216 .game_rules_values
217 .set(rule, value, ®ISTRY.game_rules)
218 }
219
220 pub fn set_erased_game_rule(&self, rule: ErasedGameRuleRef, value: GameRuleValue) -> bool {
222 let updated = self
223 .level_data
224 .write()
225 .data_mut()
226 .game_rules_values
227 .set_erased(rule, value, ®ISTRY.game_rules);
228 if updated && rule.key() == ADVANCE_TIME.key() {
229 self.broadcast_time_sync();
230 }
231 updated
232 }
233
234 pub(super) fn advance_time_with_guard(&self, guard: &LevelDataManager) -> bool {
235 self.get_game_rule_with_guard(&ADVANCE_TIME, guard)
236 }
237
238 #[must_use]
240 pub fn seed(&self) -> i64 {
241 self.level_data.read().data().seed
242 }
243
244 #[must_use]
249 #[expect(
250 clippy::missing_panics_doc,
251 reason = "panic is unreachable: SHA-256 always produces 32 bytes"
252 )]
253 pub fn obfuscated_seed(&self) -> i64 {
254 let seed = self.seed();
255 let mut hasher = Sha256::new();
256 hasher.update(seed.to_be_bytes());
257 let result = hasher.finalize();
258 let bytes: [u8; 8] = result[0..8].try_into().expect("SHA-256 produces 32 bytes");
260 i64::from_be_bytes(bytes)
261 }
262}