steel_core/player/game_mode/
player_methods.rs1use super::{
2 AttributeModifier, AttributeModifierOperation, Axis, BlockCollisionContext, BlockPos,
3 CBlockChangedAck, CChangeDifficulty, CGameEvent, CPlayerInfoUpdate,
4 CREATIVE_BLOCK_RANGE_MODIFIER_AMOUNT, CREATIVE_ENTITY_RANGE_MODIFIER_AMOUNT, CSetCamera,
5 CollisionWorld, Difficulty, Entity, FLIGHT_DISABLE_RANGE, GameEventType, GameType, Identifier,
6 LivingEntity, Player, SSpectatorAction, WorldAabb, WorldCollisionProvider,
7 player_can_change_difficulty, shapes, vanilla_attributes,
8};
9use crate::behavior::blocks::PowderSnowBlock;
10
11impl Player {
12 pub fn set_game_mode(&self, gamemode: GameType) -> bool {
16 let was_spectator = self.game_mode() == GameType::Spectator;
17 if !self.change_game_mode_state(gamemode) {
18 return false;
19 }
20
21 let flying_after_update = {
23 let mut abilities = self.abilities.lock();
24 abilities.update_for_game_mode(gamemode);
25 abilities.flying
26 };
27 if flying_after_update
28 && gamemode != GameType::Spectator
29 && self.is_in_range_of_ground_for_flight_disable()
30 {
31 self.set_flying(false);
32 }
33 self.send_abilities();
34 self.update_game_mode_invisibility();
35
36 let update_packet =
37 CPlayerInfoUpdate::update_game_mode(self.gameprofile.id, gamemode as i32);
38 self.server().broadcast_to_online(update_packet);
39
40 if gamemode == GameType::Creative {
43 self.reset_current_impulse_context();
44 }
45
46 self.send_packet(CGameEvent {
47 event: GameEventType::ChangeGameMode,
48 data: gamemode.into(),
49 });
50
51 if gamemode == GameType::Spectator {
52 self.stop_riding();
53 } else if was_spectator {
57 self.send_packet(CSetCamera {
58 camera_id: self.id(),
59 });
60 }
62
63 self.send_abilities();
64 self.update_game_mode_invisibility();
65 self.living_base.mark_effects_dirty();
66
67 true
68 }
69
70 fn update_game_mode_invisibility(&self) {
71 self.living_base.mark_effects_dirty();
72 self.update_dirty_mob_effect_entity_data();
73 self.sync_entity_data();
74 }
75
76 fn is_in_range_of_ground_for_flight_disable(&self) -> bool {
77 let world = self.get_world();
78 let collision_world = WorldCollisionProvider::for_entity(&world, self);
79 let bounding_box = self.bounding_box();
80 let collision_context =
81 BlockCollisionContext::entity(self.position().y, self.is_descending())
82 .with_fall_distance(self.fall_distance())
83 .with_can_walk_on_powder_snow(PowderSnowBlock::can_entity_walk_on_powder_snow(
84 self,
85 ));
86
87 if collision_world.has_collision_with_context(&bounding_box, collision_context) {
88 return false;
89 }
90
91 let below = WorldAabb::new(
92 bounding_box.min_x(),
93 bounding_box.min_y() - FLIGHT_DISABLE_RANGE,
94 bounding_box.min_z(),
95 bounding_box.max_x(),
96 bounding_box.min_y(),
97 bounding_box.max_z(),
98 );
99 let colliders = collision_world.get_collisions_with_context(&below, collision_context);
100 if colliders.is_empty() {
101 return false;
102 }
103
104 let available_space_below =
105 -shapes::collide(Axis::Y, &bounding_box, &colliders, -FLIGHT_DISABLE_RANGE);
106 available_space_below < FLIGHT_DISABLE_RANGE
107 }
108
109 pub fn send_difficulty(&self) {
111 let world = self.get_world();
112 let level_data = world.level_data.read();
113 let difficulty = level_data.data().difficulty;
114 let locked = level_data.data().difficulty_locked;
115 drop(level_data);
116 self.send_packet(CChangeDifficulty { difficulty, locked });
117 }
118
119 pub fn handle_change_difficulty(&self, difficulty: Difficulty) {
121 let world = self.get_world();
122 if !player_can_change_difficulty(self, &world) {
123 log::warn!(
124 "Player {} tried to change difficulty to {difficulty:?} without permission",
125 self.gameprofile.name
126 );
127 return;
128 }
129 {
130 let level_data = world.level_data.read();
131 if level_data.data().difficulty_locked {
132 let current = level_data.data().difficulty;
133 drop(level_data);
134 self.send_packet(CChangeDifficulty {
135 difficulty: current,
136 locked: true,
137 });
138 return;
139 }
140 }
141
142 let domain = self.get_world().domain().to_owned();
143 for world in self.server().worlds.worlds_in_domain(&domain) {
144 world.set_difficulty(difficulty);
145 }
146 }
147
148 pub(in crate::player) fn update_player_attributes(&self) {
153 let is_creative = self.game_mode() == GameType::Creative;
154 let mut attrs = self.attributes().lock();
155
156 if is_creative {
157 attrs.set_modifier(
158 vanilla_attributes::BLOCK_INTERACTION_RANGE,
159 AttributeModifier {
160 id: Identifier::vanilla_static("creative_mode_block_range"),
161 amount: CREATIVE_BLOCK_RANGE_MODIFIER_AMOUNT,
162 operation: AttributeModifierOperation::AddValue,
163 },
164 false,
165 );
166 attrs.set_modifier(
167 vanilla_attributes::ENTITY_INTERACTION_RANGE,
168 AttributeModifier {
169 id: Identifier::vanilla_static("creative_mode_entity_range"),
170 amount: CREATIVE_ENTITY_RANGE_MODIFIER_AMOUNT,
171 operation: AttributeModifierOperation::AddValue,
172 },
173 false,
174 );
175 } else {
176 attrs.remove_modifier(
177 vanilla_attributes::BLOCK_INTERACTION_RANGE,
178 &Identifier::vanilla_static("creative_mode_block_range"),
179 );
180 attrs.remove_modifier(
181 vanilla_attributes::ENTITY_INTERACTION_RANGE,
182 &Identifier::vanilla_static("creative_mode_entity_range"),
183 );
184 }
185 }
186
187 #[must_use]
189 pub fn has_infinite_materials(&self) -> bool {
190 self.game_mode() == GameType::Creative
191 }
192
193 pub fn ack_block_changes_up_to(&self, sequence: i32) {
198 self.tick_state.lock().ack_block_changes_up_to(sequence);
199 }
200
201 pub(in crate::player) fn tick_ack_block_changes(&self) {
203 let sequence = self.tick_state.lock().take_ack_block_changes_up_to();
204 if sequence > -1 {
205 self.send_packet(CBlockChangedAck { sequence });
206 }
207 }
208
209 #[must_use]
214 pub fn is_within_block_interaction_range(&self, pos: BlockPos) -> bool {
215 self.is_within_block_interaction_range_with_buffer(pos, 1.0)
216 }
217
218 #[must_use]
220 pub fn is_within_block_interaction_range_with_buffer(
221 &self,
222 pos: BlockPos,
223 buffer: f64,
224 ) -> bool {
225 let player_pos = self.position();
226 let eye_y = player_pos.y + self.get_eye_height();
227
228 let min_x = f64::from(pos.x());
229 let min_y = f64::from(pos.y());
230 let min_z = f64::from(pos.z());
231 let max_x = min_x + 1.0;
232 let max_y = min_y + 1.0;
233 let max_z = min_z + 1.0;
234
235 let dx = f64::max(f64::max(min_x - player_pos.x, player_pos.x - max_x), 0.0);
236 let dy = f64::max(f64::max(min_y - eye_y, eye_y - max_y), 0.0);
237 let dz = f64::max(f64::max(min_z - player_pos.z, player_pos.z - max_z), 0.0);
238 let dist_sq = dx * dx + dy * dy + dz * dz;
239
240 let base_range = self
241 .attributes()
242 .lock()
243 .get_value(vanilla_attributes::BLOCK_INTERACTION_RANGE)
244 .unwrap_or(4.5);
245 let max_range = base_range + buffer;
246 dist_sq < max_range * max_range
247 }
248
249 #[must_use]
251 pub fn is_within_entity_interaction_range(&self, aabb: WorldAabb, buffer: f64) -> bool {
252 let player_pos = self.position();
253 let eye_y = player_pos.y + self.get_eye_height();
254
255 let dx = f64::max(
256 f64::max(aabb.min_x() - player_pos.x, player_pos.x - aabb.max_x()),
257 0.0,
258 );
259 let dy = f64::max(f64::max(aabb.min_y() - eye_y, eye_y - aabb.max_y()), 0.0);
260 let dz = f64::max(
261 f64::max(aabb.min_z() - player_pos.z, player_pos.z - aabb.max_z()),
262 0.0,
263 );
264 let dist_sq = dx * dx + dy * dy + dz * dz;
265
266 let base_range = self
267 .attributes()
268 .lock()
269 .get_value(vanilla_attributes::ENTITY_INTERACTION_RANGE)
270 .unwrap_or(3.0);
271 let max_range = base_range + buffer;
272 dist_sq < max_range * max_range
273 }
274
275 pub fn handle_spectator_action(&self, packet: SSpectatorAction) {
277 if !self.has_client_loaded() || self.game_mode() != GameType::Spectator {
278 return;
279 }
280
281 let Some(entity_id) = packet.spectate_entity_id else {
282 return;
283 };
284
285 let world = self.get_world();
286 let Some(target) = world.get_accessible_entity_by_id(entity_id) else {
287 return;
288 };
289
290 if self.is_within_entity_interaction_range(target.bounding_box(), 3.0)
293 && target.is_pickable()
294 {
295 self.send_packet(CSetCamera {
296 camera_id: target.id(),
297 });
298 }
299 }
300
301 #[must_use]
303 pub fn is_secondary_use_active(&self) -> bool {
304 self.is_crouching()
305 }
306}