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,
5};
6
7impl World {
8 pub fn difficulty(&self) -> Difficulty {
10 self.level_data.read().data().difficulty
11 }
12
13 pub(crate) fn set_difficulty(&self, difficulty: Difficulty) {
15 let locked = {
16 let mut level_data = self.level_data.write();
17 level_data.data_mut().difficulty = difficulty;
18 level_data.data().difficulty_locked
19 };
20 self.broadcast_to_all(CChangeDifficulty { difficulty, locked });
21 }
22
23 pub const fn get_height(&self) -> i32 {
25 self.dimension_type.height
26 }
27
28 pub const fn get_min_y(&self) -> i32 {
30 self.dimension_type.min_y
31 }
32
33 pub const fn get_max_y(&self) -> i32 {
35 self.get_min_y() + self.get_height() - 1
36 }
37
38 pub const fn is_outside_build_height(&self, block_y: i32) -> bool {
40 block_y < self.get_min_y() || block_y > self.get_max_y()
41 }
42
43 #[expect(clippy::unused_self, reason = "this is an api function")]
45 pub const fn is_in_valid_bounds_horizontal(&self, block_pos: BlockPos) -> bool {
46 let chunk_x = SectionPos::block_to_section_coord(block_pos.0.x);
47 let chunk_z = SectionPos::block_to_section_coord(block_pos.0.z);
48 ChunkPos::is_valid(chunk_x, chunk_z)
49 }
50
51 pub const fn is_in_valid_bounds(&self, block_pos: BlockPos) -> bool {
53 !self.is_outside_build_height(block_pos.0.y)
54 && self.is_in_valid_bounds_horizontal(block_pos)
55 }
56
57 #[must_use]
59 pub const fn is_in_spawnable_bounds(block_pos: BlockPos) -> bool {
60 !Self::is_outside_spawnable_height(block_pos.0.y)
61 && Self::is_in_world_bounds_horizontal(block_pos)
62 }
63
64 pub(super) const fn is_in_world_bounds_horizontal(block_pos: BlockPos) -> bool {
65 block_pos.0.x >= -30_000_000
66 && block_pos.0.z >= -30_000_000
67 && block_pos.0.x < 30_000_000
68 && block_pos.0.z < 30_000_000
69 }
70
71 pub(super) const fn is_outside_spawnable_height(y: i32) -> bool {
72 y < -20_000_000 || y >= 20_000_000
73 }
74
75 #[must_use]
78 pub const fn max_build_height(&self) -> i32 {
79 self.get_min_y() + self.get_height()
80 }
81
82 #[must_use]
85 pub const fn may_interact(&self, _player: &Player, pos: BlockPos) -> bool {
86 self.is_in_valid_bounds(pos)
87 }
88
89 #[must_use]
96 pub fn is_unobstructed(&self, collision_shape: OffsetVoxelShape, pos: BlockPos) -> bool {
97 if collision_shape.is_empty() {
98 return true;
99 }
100
101 for block_aabb in collision_shape.iter() {
102 let world_aabb = block_aabb.at_block(pos);
103 for entity in self.get_entities_in_aabb(&world_aabb) {
104 if entity.blocks_building() && entity.bounding_box().intersects(world_aabb) {
105 return false;
106 }
107 }
108 }
109
110 true
111 }
112
113 #[must_use]
118 pub fn tick_runs_normally(&self) -> bool {
119 self.tick_runs_normally.load(Ordering::Relaxed)
120 }
121
122 pub fn set_tick_runs_normally(&self, runs_normally: bool) {
126 self.tick_runs_normally
127 .store(runs_normally, Ordering::Relaxed);
128 }
129
130 #[must_use]
132 pub(crate) fn is_handling_tick(&self) -> bool {
133 self.handling_tick.load(Ordering::Relaxed)
134 }
135
136 #[must_use]
140 pub fn get_game_rule<T: GameRuleValueType>(&self, rule: &GameRule<T>) -> T {
141 let guard = self.level_data.read();
142 self.get_game_rule_with_guard(rule, &guard)
143 }
144
145 #[expect(clippy::unused_self, reason = "this is an api function")]
147 #[must_use]
148 pub fn get_game_rule_with_guard<T: GameRuleValueType>(
149 &self,
150 rule: &GameRule<T>,
151 guard: &LevelDataManager,
152 ) -> T {
153 guard
154 .data()
155 .game_rules_values
156 .get(rule, ®ISTRY.game_rules)
157 }
158
159 #[must_use]
161 pub fn get_erased_game_rule(&self, rule: ErasedGameRuleRef) -> GameRuleValue {
162 self.level_data
163 .read()
164 .data()
165 .game_rules_values
166 .get_erased(rule, ®ISTRY.game_rules)
167 .clone()
168 }
169
170 pub fn set_game_rule<T: GameRuleValueType>(&self, rule: &GameRule<T>, value: T) -> bool {
174 let updated = {
175 let mut guard = self.level_data.write();
176 self.set_game_rule_with_guard(rule, value, &mut guard)
177 };
178 if updated && rule.key() == ADVANCE_TIME.key() {
179 self.broadcast_time_sync();
180 }
181 updated
182 }
183
184 #[expect(clippy::unused_self, reason = "this is an api function")]
186 pub fn set_game_rule_with_guard<T: GameRuleValueType>(
187 &self,
188 rule: &GameRule<T>,
189 value: T,
190 guard: &mut LevelDataManager,
191 ) -> bool {
192 guard
193 .data_mut()
194 .game_rules_values
195 .set(rule, value, ®ISTRY.game_rules)
196 }
197
198 pub fn set_erased_game_rule(&self, rule: ErasedGameRuleRef, value: GameRuleValue) -> bool {
200 let updated = self
201 .level_data
202 .write()
203 .data_mut()
204 .game_rules_values
205 .set_erased(rule, value, ®ISTRY.game_rules);
206 if updated && rule.key() == ADVANCE_TIME.key() {
207 self.broadcast_time_sync();
208 }
209 updated
210 }
211
212 pub(super) fn advance_time_with_guard(&self, guard: &LevelDataManager) -> bool {
213 self.get_game_rule_with_guard(&ADVANCE_TIME, guard)
214 }
215
216 #[must_use]
218 pub fn seed(&self) -> i64 {
219 self.level_data.read().data().seed
220 }
221
222 #[must_use]
227 #[expect(
228 clippy::missing_panics_doc,
229 reason = "panic is unreachable: SHA-256 always produces 32 bytes"
230 )]
231 pub fn obfuscated_seed(&self) -> i64 {
232 let seed = self.seed();
233 let mut hasher = Sha256::new();
234 hasher.update(seed.to_be_bytes());
235 let result = hasher.finalize();
236 let bytes: [u8; 8] = result[0..8].try_into().expect("SHA-256 produces 32 bytes");
238 i64::from_be_bytes(bytes)
239 }
240}