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