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