steel_core/world/
properties.rs1use steel_utils::types::GameType;
2
3use super::{
4 ADVANCE_TIME, BlockPos, CChangeDifficulty, ChunkPos, Difficulty, Digest, ErasedGameRuleRef,
5 GameRule, GameRuleValue, GameRuleValueType, LevelDataManager, OffsetVoxelShape, Ordering,
6 Player, REGISTRY, SectionPos, Sha256, World, vanilla_dimension_types,
7};
8
9impl World {
10 pub const MAX_LEVEL_SIZE: i32 = 30_000_000;
12 pub const MAX_ENTITY_SPAWN_Y: i32 = 20_000_000;
14 pub const MIN_ENTITY_SPAWN_Y: i32 = -20_000_000;
16
17 #[must_use]
22 pub fn is_end_dimension_type(&self) -> bool {
23 self.dimension_type == &vanilla_dimension_types::THE_END
24 }
25
26 pub fn difficulty(&self) -> Difficulty {
28 self.level_data.read().data().difficulty
29 }
30
31 pub(crate) fn set_difficulty(&self, difficulty: Difficulty) {
33 let locked = {
34 let mut level_data = self.level_data.write();
35 level_data.data_mut().difficulty = difficulty;
36 level_data.data().difficulty_locked
37 };
38 self.broadcast_to_all(CChangeDifficulty { difficulty, locked });
39 }
40
41 #[must_use]
43 pub fn default_gamemode(&self) -> GameType {
44 GameType::from(self.default_gamemode.load(Ordering::Relaxed))
45 }
46
47 pub fn set_default_gamemode(&self, default_gamemode: GameType) {
49 self.default_gamemode
50 .store(default_gamemode.into(), Ordering::Relaxed);
51 }
52
53 pub const fn get_height(&self) -> i32 {
55 self.dimension_type.height
56 }
57
58 pub const fn get_min_y(&self) -> i32 {
60 self.dimension_type.min_y
61 }
62
63 pub const fn get_max_y(&self) -> i32 {
65 self.get_min_y() + self.get_height() - 1
66 }
67
68 pub const fn is_outside_build_height(&self, block_y: i32) -> bool {
70 block_y < self.get_min_y() || block_y > self.get_max_y()
71 }
72
73 #[expect(clippy::unused_self, reason = "this is an api function")]
75 pub const fn is_in_valid_bounds_horizontal(&self, block_pos: BlockPos) -> bool {
76 let chunk_x = SectionPos::block_to_section_coord(block_pos.0.x);
77 let chunk_z = SectionPos::block_to_section_coord(block_pos.0.z);
78 ChunkPos::is_valid(chunk_x, chunk_z)
79 }
80
81 pub const fn is_in_valid_bounds(&self, block_pos: BlockPos) -> bool {
83 !self.is_outside_build_height(block_pos.0.y)
84 && self.is_in_valid_bounds_horizontal(block_pos)
85 }
86
87 pub const fn is_in_world_bounds(&self, block_pos: BlockPos) -> bool {
89 !self.is_outside_build_height(block_pos.0.y)
90 && Self::is_in_world_bounds_horizontal(block_pos)
91 }
92
93 #[must_use]
95 pub const fn is_in_spawnable_bounds(block_pos: BlockPos) -> bool {
96 !Self::is_outside_spawnable_height(block_pos.0.y)
97 && Self::is_in_world_bounds_horizontal(block_pos)
98 }
99
100 pub(super) const fn is_in_world_bounds_horizontal(block_pos: BlockPos) -> bool {
101 block_pos.0.x >= -Self::MAX_LEVEL_SIZE
102 && block_pos.0.z >= -Self::MAX_LEVEL_SIZE
103 && block_pos.0.x < Self::MAX_LEVEL_SIZE
104 && block_pos.0.z < Self::MAX_LEVEL_SIZE
105 }
106
107 pub(super) const fn is_outside_spawnable_height(y: i32) -> bool {
108 y < Self::MIN_ENTITY_SPAWN_Y || y >= Self::MAX_ENTITY_SPAWN_Y
109 }
110
111 #[must_use]
114 pub const fn max_build_height(&self) -> i32 {
115 self.get_min_y() + self.get_height()
116 }
117
118 #[must_use]
121 pub const fn may_interact(&self, _player: &Player, pos: BlockPos) -> bool {
122 self.is_in_valid_bounds(pos)
123 }
124
125 #[must_use]
132 pub fn is_unobstructed(&self, collision_shape: OffsetVoxelShape, pos: BlockPos) -> bool {
133 if collision_shape.is_empty() {
134 return true;
135 }
136
137 for block_aabb in collision_shape.iter() {
138 let world_aabb = block_aabb.at_block(pos);
139 for entity in self.get_entities_in_aabb(&world_aabb) {
140 if entity.blocks_building() && entity.bounding_box().intersects(world_aabb) {
141 return false;
142 }
143 }
144 }
145
146 true
147 }
148
149 #[must_use]
153 pub fn tick_runs_normally(&self) -> bool {
154 self.tick_runs_normally.load(Ordering::Relaxed)
155 }
156
157 pub fn set_tick_runs_normally(&self, runs_normally: bool) {
161 self.tick_runs_normally
162 .store(runs_normally, Ordering::Relaxed);
163 }
164
165 #[must_use]
167 pub(crate) fn is_handling_tick(&self) -> bool {
168 self.handling_tick.load(Ordering::Relaxed)
169 }
170
171 #[must_use]
175 pub fn get_game_rule<T: GameRuleValueType>(&self, rule: &GameRule<T>) -> T {
176 let guard = self.level_data.read();
177 self.get_game_rule_with_guard(rule, &guard)
178 }
179
180 #[expect(clippy::unused_self, reason = "this is an api function")]
182 #[must_use]
183 pub fn get_game_rule_with_guard<T: GameRuleValueType>(
184 &self,
185 rule: &GameRule<T>,
186 guard: &LevelDataManager,
187 ) -> T {
188 guard
189 .data()
190 .game_rules_values
191 .get(rule, ®ISTRY.game_rules)
192 }
193
194 #[must_use]
196 pub fn get_erased_game_rule(&self, rule: ErasedGameRuleRef) -> GameRuleValue {
197 self.level_data
198 .read()
199 .data()
200 .game_rules_values
201 .get_erased(rule, ®ISTRY.game_rules)
202 .clone()
203 }
204
205 pub fn set_game_rule<T: GameRuleValueType>(&self, rule: &GameRule<T>, value: T) -> bool {
209 let updated = {
210 let mut guard = self.level_data.write();
211 self.set_game_rule_with_guard(rule, value, &mut guard)
212 };
213 if updated && rule.key() == ADVANCE_TIME.key() {
214 self.broadcast_time_sync();
215 }
216 updated
217 }
218
219 #[expect(clippy::unused_self, reason = "this is an api function")]
221 pub fn set_game_rule_with_guard<T: GameRuleValueType>(
222 &self,
223 rule: &GameRule<T>,
224 value: T,
225 guard: &mut LevelDataManager,
226 ) -> bool {
227 guard
228 .data_mut()
229 .game_rules_values
230 .set(rule, value, ®ISTRY.game_rules)
231 }
232
233 pub fn set_erased_game_rule(&self, rule: ErasedGameRuleRef, value: GameRuleValue) -> bool {
235 let updated = self
236 .level_data
237 .write()
238 .data_mut()
239 .game_rules_values
240 .set_erased(rule, value, ®ISTRY.game_rules);
241 if updated && rule.key() == ADVANCE_TIME.key() {
242 self.broadcast_time_sync();
243 }
244 updated
245 }
246
247 pub(super) fn advance_time_with_guard(&self, guard: &LevelDataManager) -> bool {
248 self.get_game_rule_with_guard(&ADVANCE_TIME, guard)
249 }
250
251 #[must_use]
253 pub fn seed(&self) -> i64 {
254 self.level_data.read().data().seed
255 }
256
257 #[must_use]
262 #[expect(
263 clippy::missing_panics_doc,
264 reason = "panic is unreachable: SHA-256 always produces 32 bytes"
265 )]
266 pub fn obfuscated_seed(&self) -> i64 {
267 let seed = self.seed();
268 let mut hasher = Sha256::new();
269 hasher.update(seed.to_be_bytes());
270 let result = hasher.finalize();
271 let bytes: [u8; 8] = result[0..8].try_into().expect("SHA-256 produces 32 bytes");
273 i64::from_be_bytes(bytes)
274 }
275}