steel_core/player/game_mode/
block_interaction.rs1use super::{block_breaking::BlockBreakAction, *};
2
3impl Player {
4 fn send_block_updates(&self, pos: BlockPos, direction: Direction) {
8 let world = self.get_world();
9 let state = world.get_block_state(pos);
10 self.send_packet(CBlockUpdate {
11 pos,
12 block_state: state,
13 });
14
15 let neighbor_pos = direction.relative(pos);
16 let neighbor_state = world.get_block_state(neighbor_pos);
17 self.send_packet(CBlockUpdate {
18 pos: neighbor_pos,
19 block_state: neighbor_state,
20 });
21 }
22
23 pub fn swing(&self, hand: InteractionHand, update_self: bool) {
25 LivingEntity::swing(self, hand, update_self);
26 }
27
28 pub fn handle_use_item_on(&self, packet: SUseItemOn) {
32 if !self.has_client_loaded() {
33 return;
34 }
35
36 self.ack_block_changes_up_to(packet.sequence);
37
38 let pos = packet.block_hit.block_pos;
39 let direction = packet.block_hit.direction;
40
41 if !self.is_within_block_interaction_range(pos) {
42 self.send_block_updates(pos, direction);
43 return;
44 }
45
46 let center_x = f64::from(pos.x()) + 0.5;
47 let center_y = f64::from(pos.y()) + 0.5;
48 let center_z = f64::from(pos.z()) + 0.5;
49 let location = &packet.block_hit.location;
50 let limit = 1.000_000_1;
51
52 if (location.x - center_x).abs() >= limit
53 || (location.y - center_y).abs() >= limit
54 || (location.z - center_z).abs() >= limit
55 {
56 log::warn!(
57 "Rejecting UseItemOnPacket from {}: location {:?} too far from block {:?}",
58 self.gameprofile.name,
59 location,
60 pos
61 );
62 self.send_block_updates(pos, direction);
63 return;
64 }
65
66 let world = self.get_world();
67
68 if pos.y() >= world.max_build_height() {
69 self.send_block_updates(pos, direction);
71 return;
72 }
73
74 if self.is_awaiting_teleport() {
75 self.send_block_updates(pos, direction);
76 return;
77 }
78
79 if !world.may_interact(self, pos) {
80 self.send_block_updates(pos, direction);
81 return;
82 }
83
84 let result = use_item_on(self, &world, packet.hand, &packet.block_hit);
85
86 if result.should_swing_server() {
87 self.swing(packet.hand, true);
88 }
89
90 self.send_block_updates(pos, direction);
91 self.broadcast_inventory_changes();
92 }
93
94 pub fn handle_player_action(&self, packet: SPlayerAction) {
96 if !self.has_client_loaded() {
97 return;
98 }
99
100 let world = self.get_world();
101 match packet.action {
102 PlayerAction::StartDestroyBlock => {
103 self.block_breaking.lock().handle_block_break_action(
104 self,
105 &world,
106 packet.pos,
107 BlockBreakAction::Start,
108 packet.direction,
109 );
110 self.ack_block_changes_up_to(packet.sequence);
111 }
112 PlayerAction::StopDestroyBlock => {
113 self.block_breaking.lock().handle_block_break_action(
114 self,
115 &world,
116 packet.pos,
117 BlockBreakAction::Stop,
118 packet.direction,
119 );
120 self.ack_block_changes_up_to(packet.sequence);
121 }
122 PlayerAction::AbortDestroyBlock => {
123 self.block_breaking.lock().handle_block_break_action(
124 self,
125 &world,
126 packet.pos,
127 BlockBreakAction::Abort,
128 packet.direction,
129 );
130 self.ack_block_changes_up_to(packet.sequence);
131 }
132 PlayerAction::DropAllItems => {
133 self.drop_from_selected(true);
134 }
135 PlayerAction::DropItem => {
136 self.drop_from_selected(false);
137 }
138 PlayerAction::ReleaseUseItem => {
139 log::debug!("Player {} released use item", self.gameprofile.name);
141 }
142 PlayerAction::SwapItemWithOffhand => {
143 if self.game_mode() == GameType::Spectator {
144 return;
145 }
146
147 let changed = self.inventory.lock().swap_hands();
148 if changed {
149 self.broadcast_inventory_changes();
150 }
151 }
153 PlayerAction::Stab => {
154 if self.game_mode() == GameType::Spectator {
155 return;
156 }
157
158 let main_hand_item = {
159 let inventory = self.inventory.lock();
160 let stack = inventory.get_item_in_hand(InteractionHand::MainHand);
161 stack.copy_with_count(stack.count())
162 };
163 if self.cannot_attack_with_item(&main_hand_item, 5) {
164 return;
165 }
166 if let Some(piercing_weapon) = main_hand_item.get_piercing_weapon() {
167 self.piercing_attack(&main_hand_item, piercing_weapon);
168 }
169 }
170 }
171 }
172
173 pub fn handle_pick_item_from_block(&self, packet: SPickItemFromBlock) {
179 if !self.is_within_block_interaction_range(packet.pos) {
180 return;
181 }
182
183 let state = self.get_world().get_block_state(packet.pos);
184 if state.is_air() {
185 return;
186 }
187
188 let block = state.get_block();
189 let block_behaviors = &*BLOCK_BEHAVIORS;
190 let behavior = block_behaviors.get_behavior(block);
191
192 let include_data = self.has_infinite_materials() && packet.include_data;
193
194 let Some(item_stack) = behavior.get_clone_item_stack(block, state, include_data) else {
195 return;
196 };
197
198 if item_stack.is_empty() {
199 return;
200 }
201
202 let mut inventory = self.inventory.lock();
206
207 let slot_with_item = inventory.find_slot_matching_item(&item_stack);
208
209 if slot_with_item != -1 {
210 if PlayerInventory::is_hotbar_slot(slot_with_item as usize) {
211 inventory.set_selected_slot(slot_with_item as u8);
212 } else {
213 inventory.pick_slot(slot_with_item);
214 }
215 } else if self.has_infinite_materials() {
216 inventory.add_and_pick_item(item_stack);
217 } else {
218 return;
219 }
220
221 self.send_packet(CSetHeldSlot {
222 slot: i32::from(inventory.get_selected_slot()),
223 });
224
225 drop(inventory);
226 self.inventory_menu
227 .lock()
228 .behavior_mut()
229 .broadcast_changes(&self.connection);
230 }
231
232 pub fn handle_sign_update(&self, packet: SSignUpdate) {
234 if !self.is_within_block_interaction_range(packet.pos) {
235 return;
236 }
237
238 let world = self.get_world();
239
240 let Some(block_entity) = world.get_block_entity(packet.pos) else {
241 return;
242 };
243
244 let Some(sign) = block_entity.downcast_ref::<SignBlockEntity>() else {
245 return;
246 };
247
248 if sign.is_waxed() {
249 return;
250 }
251
252 if sign.get_player_who_may_edit() != Some(self.gameprofile.id) {
253 log::warn!(
254 "Player {} tried to edit sign they're not allowed to edit",
255 self.gameprofile.name
256 );
257 return;
258 }
259
260 let mut text = sign.get_text(packet.is_front_text);
261 for (i, line) in packet.lines.iter().enumerate() {
262 if i < 4 {
263 let stripped = strip_formatting_codes(line);
264 text.set_message(i, TextComponent::plain(stripped));
265 }
266 }
267
268 sign.set_text(text, packet.is_front_text);
269 sign.set_player_who_may_edit(None);
270 sign.set_changed();
271
272 let update_tag = sign.get_update_tag();
273 let block_entity_type = sign.get_type();
274 let pos = packet.pos;
275
276 if let Some(nbt) = update_tag {
277 world.broadcast_block_entity_update(pos, block_entity_type, nbt);
278 }
279 }
280
281 pub fn open_sign_editor(&self, pos: BlockPos, is_front_text: bool) {
287 let world = self.get_world();
288
289 if let Some(block_entity) = world.get_block_entity(pos)
290 && let Some(sign) = block_entity.downcast_ref::<SignBlockEntity>()
291 {
292 sign.set_player_who_may_edit(Some(self.gameprofile.id));
293 }
294
295 let state = world.get_block_state(pos);
296 self.send_packet(CBlockUpdate {
297 pos,
298 block_state: state,
299 });
300
301 self.send_packet(COpenSignEditor { pos, is_front_text });
302 }
303}
304
305fn strip_formatting_codes(text: &str) -> String {
309 let mut result = String::with_capacity(text.len());
310 let mut chars = text.chars().peekable();
311
312 while let Some(c) = chars.next() {
313 if c == '§' {
314 chars.next();
315 } else {
316 result.push(c);
317 }
318 }
319
320 result
321}