Skip to main content

kojacoord_protocol/versions/v1_8/play/
mod.rs

1use bytes::{Buf, BufMut, Bytes, BytesMut};
2
3use crate::codec::{Decode, Encode, PacketId};
4use crate::error::ProtocolError;
5use crate::types::slot::LegacySlot;
6use crate::types::VarInt;
7
8pub use packets::{
9    ClientboundAwardStats, ClientboundBlockAction, ClientboundBlockDestroyStage,
10    ClientboundBlockEntityData, ClientboundBlockUpdate, ClientboundChangeDifficulty,
11    ClientboundChatMessage, ClientboundCommandSuggestions, ClientboundContainerClose,
12    ClientboundContainerSetContent, ClientboundContainerSetProperty, ClientboundContainerSetSlot,
13    ClientboundDisconnect, ClientboundEntityAnimation, ClientboundEntityEvent,
14    ClientboundExplosion, ClientboundForgetLevelChunk, ClientboundGameEvent,
15    ClientboundInitializeBorder, ClientboundJoinGame, ClientboundKeepAlive,
16    ClientboundLevelChunkWithLight, ClientboundLevelEvent, ClientboundLevelParticles,
17    ClientboundLoginPlay, ClientboundMapItemData, ClientboundMoveEntityPos,
18    ClientboundMoveEntityPosRot, ClientboundMoveEntityRot, ClientboundOpenScreen,
19    ClientboundOpenSignEditor, ClientboundPlayerAbilities, ClientboundPlayerCombatEnd,
20    ClientboundPlayerCombatEnter, ClientboundPlayerCombatKill, ClientboundPlayerInfoUpdate,
21    ClientboundPlayerPosition, ClientboundPluginMessage, ClientboundRemoveEntities,
22    ClientboundRemoveEntityEffect, ClientboundResetScore, ClientboundResourcePackPush,
23    ClientboundRespawn, ClientboundRotateHead, ClientboundSectionBlocksUpdate,
24    ClientboundSetBorderCenter, ClientboundSetBorderLerpSize, ClientboundSetBorderSize,
25    ClientboundSetBorderWarningDelay, ClientboundSetBorderWarningDistance, ClientboundSetCamera,
26    ClientboundSetEntityLink, ClientboundSetEntityMotion, ClientboundSetEquipment,
27    ClientboundSetExperience, ClientboundSetHealth, ClientboundSetHeldItem,
28    ClientboundSetScoreboardObjective, ClientboundSetScoreboardScore, ClientboundSetTime,
29    ClientboundSound, ClientboundSpawnEntity, ClientboundSpawnExperienceOrb,
30    ClientboundSpawnPlayer, ClientboundTabList, ClientboundTakeItemEntity,
31    ClientboundTeleportEntity, ClientboundUpdateAttributes, ClientboundUpdateEffects,
32    InteractAction, ServerboundAnimation, ServerboundChatMessage, ServerboundClickWindow,
33    ServerboundClickWindowButton, ServerboundClientSettings, ServerboundClientStatus,
34    ServerboundCloseWindow, ServerboundCommandSuggestion, ServerboundConfirmTransaction,
35    ServerboundCreativeInventoryAction, ServerboundEnchantItem, ServerboundEntityAction,
36    ServerboundHeldItemChange, ServerboundInteract, ServerboundKeepAlive, ServerboundMovePlayerPos,
37    ServerboundMovePlayerPosRot, ServerboundMovePlayerRot, ServerboundMovePlayerStatusOnly,
38    ServerboundPlayerAbilities, ServerboundPlayerAction, ServerboundPlayerBlockPlacement,
39    ServerboundPluginMessage, ServerboundResourcePackStatus, ServerboundSpectate,
40    ServerboundSteerVehicle, ServerboundUpdateSign,
41};
42
43mod packets {
44    use super::*;
45
46    fn encode_str(s: &str, dst: &mut BytesMut) -> Result<(), ProtocolError> {
47        let bytes = s.as_bytes();
48        VarInt(bytes.len() as i32).encode(dst)?;
49        dst.put_slice(bytes);
50        Ok(())
51    }
52
53    fn decode_str(src: &mut Bytes, ctx: &'static str) -> Result<String, ProtocolError> {
54        let len = VarInt::decode(src)?.0 as usize;
55        if src.remaining() < len {
56            return Err(ProtocolError::Io(std::io::Error::new(
57                std::io::ErrorKind::UnexpectedEof,
58                format!("Missing bytes for {ctx}"),
59            )));
60        }
61        let mut b = vec![0u8; len];
62        src.copy_to_slice(&mut b);
63        String::from_utf8(b).map_err(|_| {
64            ProtocolError::Io(std::io::Error::new(
65                std::io::ErrorKind::InvalidData,
66                format!("Invalid UTF-8 in {ctx}"),
67            ))
68        })
69    }
70
71    fn need(src: &Bytes, n: usize) -> Result<(), ProtocolError> {
72        if src.remaining() < n {
73            Err(ProtocolError::Io(std::io::Error::new(
74                std::io::ErrorKind::UnexpectedEof,
75                format!("Need {n} bytes, have {}", src.remaining()),
76            )))
77        } else {
78            Ok(())
79        }
80    }
81
82    // ── Raw opaque packet macro ───────────────────────────────────────────────
83
84    macro_rules! raw_packet {
85        ($name:ident, $id:expr) => {
86            #[derive(Debug, Clone, PartialEq)]
87            pub struct $name {
88                pub raw: Vec<u8>,
89            }
90            impl PacketId for $name {
91                fn packet_id(_ver: u32) -> u8 {
92                    $id
93                }
94            }
95            impl Encode for $name {
96                fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
97                    dst.put_slice(&self.raw);
98                    Ok(())
99                }
100            }
101            impl Decode for $name {
102                fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
103                    let len = src.remaining();
104                    let mut raw = vec![0u8; len];
105                    src.copy_to_slice(&mut raw);
106                    Ok(Self { raw })
107                }
108            }
109        };
110    }
111
112    // ── JoinGame (0x01) ───────────────────────────────────────────────────────
113
114    #[derive(Debug, Clone, PartialEq)]
115    pub struct ClientboundJoinGame {
116        pub entity_id: i32,
117        pub game_mode: u8,
118        pub dimension: i8,
119        pub difficulty: u8,
120        pub max_players: u8,
121        pub level_type: String,
122        pub reduced_debug_info: bool,
123    }
124
125    impl PacketId for ClientboundJoinGame {
126        fn packet_id(_ver: u32) -> u8 {
127            0x01
128        }
129    }
130
131    impl Encode for ClientboundJoinGame {
132        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
133            dst.put_i32(self.entity_id);
134            dst.put_u8(self.game_mode);
135            dst.put_i8(self.dimension);
136            dst.put_u8(self.difficulty);
137            dst.put_u8(self.max_players);
138            encode_str(&self.level_type, dst)?;
139            dst.put_u8(self.reduced_debug_info as u8);
140            Ok(())
141        }
142    }
143
144    impl Decode for ClientboundJoinGame {
145        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
146            need(src, 4 + 1 + 1 + 1 + 1)?;
147            let entity_id = src.get_i32();
148            let game_mode = src.get_u8();
149            let dimension = src.get_i8();
150            let difficulty = src.get_u8();
151            let max_players = src.get_u8();
152            let level_type = decode_str(src, "ClientboundJoinGame level_type")?;
153            need(src, 1)?;
154            let reduced_debug_info = src.get_u8() != 0;
155            Ok(Self {
156                entity_id,
157                game_mode,
158                dimension,
159                difficulty,
160                max_players,
161                level_type,
162                reduced_debug_info,
163            })
164        }
165    }
166
167    // LoginPlay is an alias for JoinGame used by the proxy layer — kept as raw.
168    raw_packet!(ClientboundLoginPlay, 0x01);
169
170    // ── Respawn (0x07) ────────────────────────────────────────────────────────
171
172    #[derive(Debug, Clone, PartialEq)]
173    pub struct ClientboundRespawn {
174        pub dimension: i32,
175        pub difficulty: u8,
176        pub game_mode: u8,
177        pub level_type: String,
178    }
179
180    impl PacketId for ClientboundRespawn {
181        fn packet_id(_ver: u32) -> u8 {
182            0x07
183        }
184    }
185
186    impl Encode for ClientboundRespawn {
187        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
188            dst.put_i32(self.dimension);
189            dst.put_u8(self.difficulty);
190            dst.put_u8(self.game_mode);
191            encode_str(&self.level_type, dst)
192        }
193    }
194
195    impl Decode for ClientboundRespawn {
196        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
197            need(src, 4 + 1 + 1)?;
198            let dimension = src.get_i32();
199            let difficulty = src.get_u8();
200            let game_mode = src.get_u8();
201            let level_type = decode_str(src, "ClientboundRespawn level_type")?;
202            Ok(Self {
203                dimension,
204                difficulty,
205                game_mode,
206                level_type,
207            })
208        }
209    }
210
211    // ── PlayerPosition (0x08) ─────────────────────────────────────────────────
212
213    #[derive(Debug, Clone, PartialEq)]
214    pub struct ClientboundPlayerPosition {
215        pub x: f64,
216        pub head_y: f64,
217        pub z: f64,
218        pub yaw: f32,
219        pub pitch: f32,
220        pub flags: u8,
221    }
222
223    impl PacketId for ClientboundPlayerPosition {
224        fn packet_id(_ver: u32) -> u8 {
225            0x08
226        }
227    }
228
229    impl Encode for ClientboundPlayerPosition {
230        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
231            dst.put_f64(self.x);
232            dst.put_f64(self.head_y);
233            dst.put_f64(self.z);
234            dst.put_f32(self.yaw);
235            dst.put_f32(self.pitch);
236            dst.put_u8(self.flags);
237            Ok(())
238        }
239    }
240
241    impl Decode for ClientboundPlayerPosition {
242        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
243            need(src, 8 + 8 + 8 + 4 + 4 + 1)?;
244            Ok(Self {
245                x: src.get_f64(),
246                head_y: src.get_f64(),
247                z: src.get_f64(),
248                yaw: src.get_f32(),
249                pitch: src.get_f32(),
250                flags: src.get_u8(),
251            })
252        }
253    }
254
255    // ── KeepAlive (0x00 / 0x00) ───────────────────────────────────────────────
256
257    #[derive(Debug, Clone, PartialEq)]
258    pub struct ClientboundKeepAlive {
259        pub keep_alive_id: VarInt,
260    }
261
262    impl PacketId for ClientboundKeepAlive {
263        fn packet_id(_ver: u32) -> u8 {
264            0x00
265        }
266    }
267
268    impl Encode for ClientboundKeepAlive {
269        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
270            self.keep_alive_id.encode(dst)
271        }
272    }
273
274    impl Decode for ClientboundKeepAlive {
275        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
276            Ok(Self {
277                keep_alive_id: VarInt::decode(src)?,
278            })
279        }
280    }
281
282    #[derive(Debug, Clone, PartialEq)]
283    pub struct ServerboundKeepAlive {
284        pub keep_alive_id: VarInt,
285    }
286
287    impl PacketId for ServerboundKeepAlive {
288        fn packet_id(_ver: u32) -> u8 {
289            0x00
290        }
291    }
292
293    impl Encode for ServerboundKeepAlive {
294        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
295            self.keep_alive_id.encode(dst)
296        }
297    }
298
299    impl Decode for ServerboundKeepAlive {
300        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
301            Ok(Self {
302                keep_alive_id: VarInt::decode(src)?,
303            })
304        }
305    }
306
307    // ── Chat (0x02 / 0x01) ────────────────────────────────────────────────────
308
309    #[derive(Debug, Clone, PartialEq)]
310    pub struct ClientboundChatMessage {
311        pub json_message: String,
312        pub position: u8,
313    }
314
315    impl PacketId for ClientboundChatMessage {
316        fn packet_id(_ver: u32) -> u8 {
317            0x02
318        }
319    }
320
321    impl Encode for ClientboundChatMessage {
322        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
323            encode_str(&self.json_message, dst)?;
324            dst.put_u8(self.position);
325            Ok(())
326        }
327    }
328
329    impl Decode for ClientboundChatMessage {
330        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
331            let json_message = decode_str(src, "ClientboundChatMessage json_message")?;
332            need(src, 1)?;
333            Ok(Self {
334                json_message,
335                position: src.get_u8(),
336            })
337        }
338    }
339
340    #[derive(Debug, Clone, PartialEq)]
341    pub struct ServerboundChatMessage {
342        pub message: String,
343    }
344
345    impl PacketId for ServerboundChatMessage {
346        fn packet_id(_ver: u32) -> u8 {
347            0x01
348        }
349    }
350
351    impl Encode for ServerboundChatMessage {
352        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
353            encode_str(&self.message, dst)
354        }
355    }
356
357    impl Decode for ServerboundChatMessage {
358        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
359            Ok(Self {
360                message: decode_str(src, "ServerboundChatMessage message")?,
361            })
362        }
363    }
364
365    // ── PluginMessage (0x3F / 0x17) ───────────────────────────────────────────
366
367    #[derive(Debug, Clone, PartialEq)]
368    pub struct ClientboundPluginMessage {
369        pub channel: String,
370        pub data: Vec<u8>,
371    }
372
373    impl PacketId for ClientboundPluginMessage {
374        fn packet_id(_ver: u32) -> u8 {
375            0x3F
376        }
377    }
378
379    impl Encode for ClientboundPluginMessage {
380        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
381            encode_str(&self.channel, dst)?;
382            dst.put_slice(&self.data);
383            Ok(())
384        }
385    }
386
387    impl Decode for ClientboundPluginMessage {
388        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
389            let channel = decode_str(src, "ClientboundPluginMessage channel")?;
390            let len = src.remaining();
391            let mut data = vec![0u8; len];
392            src.copy_to_slice(&mut data);
393            Ok(Self { channel, data })
394        }
395    }
396
397    #[derive(Debug, Clone, PartialEq)]
398    pub struct ServerboundPluginMessage {
399        pub channel: String,
400        pub data: Vec<u8>,
401    }
402
403    impl PacketId for ServerboundPluginMessage {
404        fn packet_id(_ver: u32) -> u8 {
405            0x17
406        }
407    }
408
409    impl Encode for ServerboundPluginMessage {
410        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
411            encode_str(&self.channel, dst)?;
412            dst.put_slice(&self.data);
413            Ok(())
414        }
415    }
416
417    impl Decode for ServerboundPluginMessage {
418        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
419            let channel = decode_str(src, "ServerboundPluginMessage channel")?;
420            let len = src.remaining();
421            let mut data = vec![0u8; len];
422            src.copy_to_slice(&mut data);
423            Ok(Self { channel, data })
424        }
425    }
426
427    // ── Disconnect (0x40) ─────────────────────────────────────────────────────
428
429    #[derive(Debug, Clone, PartialEq)]
430    pub struct ClientboundDisconnect {
431        pub reason: String,
432    }
433
434    impl PacketId for ClientboundDisconnect {
435        fn packet_id(_ver: u32) -> u8 {
436            0x40
437        }
438    }
439
440    impl Encode for ClientboundDisconnect {
441        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
442            encode_str(&self.reason, dst)
443        }
444    }
445
446    impl Decode for ClientboundDisconnect {
447        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
448            Ok(Self {
449                reason: decode_str(src, "ClientboundDisconnect reason")?,
450            })
451        }
452    }
453
454    // ── SetHeldItem (0x09) ────────────────────────────────────────────────────
455
456    #[derive(Debug, Clone, PartialEq)]
457    pub struct ClientboundSetHeldItem {
458        pub slot: u8,
459    }
460
461    impl PacketId for ClientboundSetHeldItem {
462        fn packet_id(_ver: u32) -> u8 {
463            0x09
464        }
465    }
466
467    impl Encode for ClientboundSetHeldItem {
468        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
469            dst.put_u8(self.slot);
470            Ok(())
471        }
472    }
473
474    impl Decode for ClientboundSetHeldItem {
475        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
476            need(src, 1)?;
477            Ok(Self { slot: src.get_u8() })
478        }
479    }
480
481    // ── Interact (0x02) ───────────────────────────────────────────────────────
482
483    #[derive(Debug, Clone, PartialEq)]
484    pub enum InteractAction {
485        Interact,
486        Attack,
487        InteractAt {
488            target_x: f32,
489            target_y: f32,
490            target_z: f32,
491        },
492    }
493
494    #[derive(Debug, Clone, PartialEq)]
495    pub struct ServerboundInteract {
496        pub entity_id: VarInt,
497        pub action: InteractAction,
498    }
499
500    impl PacketId for ServerboundInteract {
501        fn packet_id(_ver: u32) -> u8 {
502            0x02
503        }
504    }
505
506    impl Encode for ServerboundInteract {
507        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
508            self.entity_id.encode(dst)?;
509            match &self.action {
510                InteractAction::Interact => {
511                    VarInt(0).encode(dst)?;
512                },
513                InteractAction::Attack => {
514                    VarInt(1).encode(dst)?;
515                },
516                InteractAction::InteractAt {
517                    target_x,
518                    target_y,
519                    target_z,
520                } => {
521                    VarInt(2).encode(dst)?;
522                    dst.put_f32(*target_x);
523                    dst.put_f32(*target_y);
524                    dst.put_f32(*target_z);
525                },
526            }
527            Ok(())
528        }
529    }
530
531    impl Decode for ServerboundInteract {
532        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
533            let entity_id = VarInt::decode(src)?;
534            let action = match VarInt::decode(src)?.0 {
535                0 => InteractAction::Interact,
536                1 => InteractAction::Attack,
537                2 => {
538                    need(src, 4 + 4 + 4)?;
539                    InteractAction::InteractAt {
540                        target_x: src.get_f32(),
541                        target_y: src.get_f32(),
542                        target_z: src.get_f32(),
543                    }
544                },
545                _ => {
546                    return Err(ProtocolError::Io(std::io::Error::new(
547                        std::io::ErrorKind::InvalidData,
548                        "Unknown ServerboundInteract action type",
549                    )))
550                },
551            };
552            Ok(Self { entity_id, action })
553        }
554    }
555
556    // ── Movement (0x03–0x06) ──────────────────────────────────────────────────
557
558    #[derive(Debug, Clone, PartialEq)]
559    pub struct ServerboundMovePlayerStatusOnly {
560        pub on_ground: bool,
561    }
562
563    impl PacketId for ServerboundMovePlayerStatusOnly {
564        fn packet_id(_ver: u32) -> u8 {
565            0x03
566        }
567    }
568
569    impl Encode for ServerboundMovePlayerStatusOnly {
570        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
571            dst.put_u8(self.on_ground as u8);
572            Ok(())
573        }
574    }
575
576    impl Decode for ServerboundMovePlayerStatusOnly {
577        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
578            need(src, 1)?;
579            Ok(Self {
580                on_ground: src.get_u8() != 0,
581            })
582        }
583    }
584
585    #[derive(Debug, Clone, PartialEq)]
586    pub struct ServerboundMovePlayerPos {
587        pub x: f64,
588        pub feet_y: f64,
589        pub z: f64,
590        pub on_ground: bool,
591    }
592
593    impl PacketId for ServerboundMovePlayerPos {
594        fn packet_id(_ver: u32) -> u8 {
595            0x04
596        }
597    }
598
599    impl Encode for ServerboundMovePlayerPos {
600        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
601            dst.put_f64(self.x);
602            dst.put_f64(self.feet_y);
603            dst.put_f64(self.z);
604            dst.put_u8(self.on_ground as u8);
605            Ok(())
606        }
607    }
608
609    impl Decode for ServerboundMovePlayerPos {
610        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
611            need(src, 8 + 8 + 8 + 1)?;
612            Ok(Self {
613                x: src.get_f64(),
614                feet_y: src.get_f64(),
615                z: src.get_f64(),
616                on_ground: src.get_u8() != 0,
617            })
618        }
619    }
620
621    #[derive(Debug, Clone, PartialEq)]
622    pub struct ServerboundMovePlayerRot {
623        pub yaw: f32,
624        pub pitch: f32,
625        pub on_ground: bool,
626    }
627
628    impl PacketId for ServerboundMovePlayerRot {
629        fn packet_id(_ver: u32) -> u8 {
630            0x05
631        }
632    }
633
634    impl Encode for ServerboundMovePlayerRot {
635        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
636            dst.put_f32(self.yaw);
637            dst.put_f32(self.pitch);
638            dst.put_u8(self.on_ground as u8);
639            Ok(())
640        }
641    }
642
643    impl Decode for ServerboundMovePlayerRot {
644        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
645            need(src, 4 + 4 + 1)?;
646            Ok(Self {
647                yaw: src.get_f32(),
648                pitch: src.get_f32(),
649                on_ground: src.get_u8() != 0,
650            })
651        }
652    }
653
654    #[derive(Debug, Clone, PartialEq)]
655    pub struct ServerboundMovePlayerPosRot {
656        pub x: f64,
657        pub feet_y: f64,
658        pub z: f64,
659        pub yaw: f32,
660        pub pitch: f32,
661        pub on_ground: bool,
662    }
663
664    impl PacketId for ServerboundMovePlayerPosRot {
665        fn packet_id(_ver: u32) -> u8 {
666            0x06
667        }
668    }
669
670    impl Encode for ServerboundMovePlayerPosRot {
671        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
672            dst.put_f64(self.x);
673            dst.put_f64(self.feet_y);
674            dst.put_f64(self.z);
675            dst.put_f32(self.yaw);
676            dst.put_f32(self.pitch);
677            dst.put_u8(self.on_ground as u8);
678            Ok(())
679        }
680    }
681
682    impl Decode for ServerboundMovePlayerPosRot {
683        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
684            need(src, 8 + 8 + 8 + 4 + 4 + 1)?;
685            Ok(Self {
686                x: src.get_f64(),
687                feet_y: src.get_f64(),
688                z: src.get_f64(),
689                yaw: src.get_f32(),
690                pitch: src.get_f32(),
691                on_ground: src.get_u8() != 0,
692            })
693        }
694    }
695
696    #[derive(Debug, Clone, PartialEq)]
697    pub struct ClientboundSound {
698        pub sound_name: String,
699        pub x: i32,
700        pub y: i32,
701        pub z: i32,
702        pub volume: f32,
703        pub pitch: u8,
704    }
705
706    impl PacketId for ClientboundSound {
707        fn packet_id(_ver: u32) -> u8 {
708            0x29
709        }
710    }
711
712    impl Encode for ClientboundSound {
713        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
714            encode_str(&self.sound_name, dst)?;
715            dst.put_i32(self.x);
716            dst.put_i32(self.y);
717            dst.put_i32(self.z);
718            dst.put_f32(self.volume);
719            dst.put_u8(self.pitch);
720            Ok(())
721        }
722    }
723
724    impl Decode for ClientboundSound {
725        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
726            let sound_name = decode_str(src, "MinecraftNamedSound context")?;
727            need(src, 4 + 4 + 4 + 4 + 1)?;
728            Ok(Self {
729                sound_name,
730                x: src.get_i32(),
731                y: src.get_i32(),
732                z: src.get_i32(),
733                volume: src.get_f32(),
734                pitch: src.get_u8(),
735            })
736        }
737    }
738
739    // ── Animation (0x0A) — zero-byte serverbound packet ───────────────────────
740
741    #[derive(Debug, Clone, PartialEq)]
742    pub struct ServerboundAnimation;
743
744    impl PacketId for ServerboundAnimation {
745        fn packet_id(_ver: u32) -> u8 {
746            0x0A
747        }
748    }
749
750    impl Encode for ServerboundAnimation {
751        fn encode(&self, _dst: &mut BytesMut) -> Result<(), ProtocolError> {
752            Ok(())
753        }
754    }
755
756    impl Decode for ServerboundAnimation {
757        fn decode(_src: &mut Bytes) -> Result<Self, ProtocolError> {
758            Ok(Self)
759        }
760    }
761
762    // ── Player Abilities (0x39) ───────────────────────────────────────────────────
763
764    #[derive(Debug, Clone, PartialEq)]
765    pub struct ClientboundPlayerAbilities {
766        pub flags: u8,
767        pub flying_speed: f32,
768        pub field_of_view_modifier: f32,
769    }
770
771    impl PacketId for ClientboundPlayerAbilities {
772        fn packet_id(_ver: u32) -> u8 {
773            0x39
774        }
775    }
776
777    impl Encode for ClientboundPlayerAbilities {
778        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
779            dst.put_u8(self.flags);
780            dst.put_f32(self.flying_speed);
781            dst.put_f32(self.field_of_view_modifier);
782            Ok(())
783        }
784    }
785
786    impl Decode for ClientboundPlayerAbilities {
787        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
788            need(src, 1 + 4 + 4)?;
789            Ok(Self {
790                flags: src.get_u8(),
791                flying_speed: src.get_f32(),
792                field_of_view_modifier: src.get_f32(),
793            })
794        }
795    }
796
797    // ── Opaque raw stubs ──────────────────────────────────────────────────────
798
799    raw_packet!(ClientboundSpawnEntity, 0x0E);
800    raw_packet!(ClientboundSpawnExperienceOrb, 0x11);
801    raw_packet!(ClientboundSpawnPlayer, 0x0C);
802    raw_packet!(ClientboundEntityAnimation, 0x0B);
803    raw_packet!(ClientboundAwardStats, 0x37);
804    raw_packet!(ClientboundBlockDestroyStage, 0x25);
805    raw_packet!(ClientboundBlockEntityData, 0x35);
806    raw_packet!(ClientboundBlockAction, 0x24);
807    raw_packet!(ClientboundBlockUpdate, 0x23);
808    raw_packet!(ClientboundChangeDifficulty, 0x41);
809    raw_packet!(ClientboundCommandSuggestions, 0x3A);
810    raw_packet!(ClientboundContainerClose, 0x2E);
811    raw_packet!(ClientboundContainerSetProperty, 0x31);
812
813    // ── ContainerSetContent (0x30) ────────────────────────────────────────────
814
815    #[derive(Debug, Clone, PartialEq)]
816    pub struct ClientboundContainerSetContent {
817        pub window_id: u8,
818        pub slots: Vec<LegacySlot>,
819        pub carried_item: LegacySlot,
820    }
821
822    impl PacketId for ClientboundContainerSetContent {
823        fn packet_id(_ver: u32) -> u8 {
824            0x30
825        }
826    }
827
828    impl Encode for ClientboundContainerSetContent {
829        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
830            dst.put_u8(self.window_id);
831            dst.put_i16(self.slots.len() as i16);
832            for slot in &self.slots {
833                slot.encode(dst)?;
834            }
835            self.carried_item.encode(dst)?;
836            Ok(())
837        }
838    }
839
840    impl Decode for ClientboundContainerSetContent {
841        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
842            need(src, 1 + 2)?;
843            let window_id = src.get_u8();
844            let count = src.get_i16() as usize;
845            let mut slots = Vec::with_capacity(count);
846            for _ in 0..count {
847                slots.push(LegacySlot::decode(src)?);
848            }
849            let carried_item = LegacySlot::decode(src)?;
850            Ok(Self {
851                window_id,
852                slots,
853                carried_item,
854            })
855        }
856    }
857
858    // ── ContainerSetSlot (0x2F) ───────────────────────────────────────────────
859
860    #[derive(Debug, Clone, PartialEq)]
861    pub struct ClientboundContainerSetSlot {
862        pub window_id: i8,
863        pub slot: i16,
864        pub slot_data: LegacySlot,
865    }
866
867    impl PacketId for ClientboundContainerSetSlot {
868        fn packet_id(_ver: u32) -> u8 {
869            0x2F
870        }
871    }
872
873    impl Encode for ClientboundContainerSetSlot {
874        fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
875            dst.put_i8(self.window_id);
876            dst.put_i16(self.slot);
877            self.slot_data.encode(dst)?;
878            Ok(())
879        }
880    }
881
882    impl Decode for ClientboundContainerSetSlot {
883        fn decode(src: &mut Bytes) -> Result<Self, ProtocolError> {
884            need(src, 1 + 2)?;
885            let window_id = src.get_i8();
886            let slot = src.get_i16();
887            let slot_data = LegacySlot::decode(src)?;
888            Ok(Self {
889                window_id,
890                slot,
891                slot_data,
892            })
893        }
894    }
895
896    raw_packet!(ClientboundEntityEvent, 0x1A);
897    raw_packet!(ClientboundExplosion, 0x27);
898    raw_packet!(ClientboundForgetLevelChunk, 0x26);
899    raw_packet!(ClientboundGameEvent, 0x2B);
900    raw_packet!(ClientboundInitializeBorder, 0x44);
901    raw_packet!(ClientboundLevelChunkWithLight, 0x21);
902    raw_packet!(ClientboundLevelEvent, 0x28);
903    raw_packet!(ClientboundLevelParticles, 0x2A);
904    raw_packet!(ClientboundMapItemData, 0x34);
905    raw_packet!(ClientboundMoveEntityPos, 0x15);
906    raw_packet!(ClientboundMoveEntityPosRot, 0x17);
907    raw_packet!(ClientboundMoveEntityRot, 0x16);
908    raw_packet!(ClientboundOpenScreen, 0x2D);
909    raw_packet!(ClientboundOpenSignEditor, 0x36);
910    raw_packet!(ClientboundPlayerCombatEnd, 0x42);
911    raw_packet!(ClientboundPlayerCombatEnter, 0x42);
912    raw_packet!(ClientboundPlayerCombatKill, 0x42);
913    raw_packet!(ClientboundPlayerInfoUpdate, 0x38);
914    raw_packet!(ClientboundRemoveEntities, 0x13);
915    raw_packet!(ClientboundRemoveEntityEffect, 0x1E);
916    raw_packet!(ClientboundResetScore, 0x3B);
917    raw_packet!(ClientboundResourcePackPush, 0x48);
918    raw_packet!(ClientboundRotateHead, 0x19);
919    raw_packet!(ClientboundSectionBlocksUpdate, 0x22);
920    raw_packet!(ClientboundSetBorderCenter, 0x44);
921    raw_packet!(ClientboundSetBorderLerpSize, 0x44);
922    raw_packet!(ClientboundSetBorderSize, 0x44);
923    raw_packet!(ClientboundSetBorderWarningDelay, 0x44);
924    raw_packet!(ClientboundSetBorderWarningDistance, 0x44);
925    raw_packet!(ClientboundSetCamera, 0x43);
926    raw_packet!(ClientboundSetEntityLink, 0x1B);
927    raw_packet!(ClientboundSetEntityMotion, 0x12);
928    raw_packet!(ClientboundSetEquipment, 0x04);
929    raw_packet!(ClientboundSetExperience, 0x1F);
930    raw_packet!(ClientboundSetHealth, 0x06);
931    raw_packet!(ClientboundSetScoreboardObjective, 0x3B);
932    raw_packet!(ClientboundSetScoreboardScore, 0x3C);
933    raw_packet!(ClientboundSetTime, 0x03);
934    raw_packet!(ClientboundTabList, 0x47);
935    raw_packet!(ClientboundTakeItemEntity, 0x0D);
936    raw_packet!(ClientboundTeleportEntity, 0x18);
937    raw_packet!(ClientboundUpdateAttributes, 0x20);
938    raw_packet!(ClientboundUpdateEffects, 0x1D);
939
940    raw_packet!(ServerboundPlayerBlockPlacement, 0x08);
941    raw_packet!(ServerboundClientSettings, 0x15);
942    raw_packet!(ServerboundClickWindowButton, 0x11);
943    raw_packet!(ServerboundClickWindow, 0x0E);
944    raw_packet!(ServerboundCloseWindow, 0x0D);
945    raw_packet!(ServerboundCommandSuggestion, 0x14);
946    raw_packet!(ServerboundConfirmTransaction, 0x0F);
947    raw_packet!(ServerboundCreativeInventoryAction, 0x10);
948    raw_packet!(ServerboundEnchantItem, 0x11);
949    raw_packet!(ServerboundEntityAction, 0x0B);
950    raw_packet!(ServerboundHeldItemChange, 0x09);
951    raw_packet!(ServerboundPlayerAbilities, 0x13);
952    raw_packet!(ServerboundPlayerAction, 0x07);
953    raw_packet!(ServerboundResourcePackStatus, 0x19);
954    raw_packet!(ServerboundClientStatus, 0x16);
955    raw_packet!(ServerboundSpectate, 0x18);
956    raw_packet!(ServerboundSteerVehicle, 0x0C);
957    raw_packet!(ServerboundUpdateSign, 0x12);
958}
959
960#[cfg(test)]
961mod tests {
962    use super::*;
963
964    macro_rules! roundtrip {
965        ($T:ty, $val:expr) => {{
966            let v = $val;
967            let mut buf = BytesMut::new();
968            v.encode(&mut buf).unwrap();
969            let mut b = buf.freeze();
970            assert_eq!(<$T>::decode(&mut b).unwrap(), v);
971        }};
972    }
973
974    #[test]
975    fn join_game_roundtrip() {
976        roundtrip!(
977            ClientboundJoinGame,
978            ClientboundJoinGame {
979                entity_id: 1,
980                game_mode: 0,
981                dimension: 0,
982                difficulty: 1,
983                max_players: 20,
984                level_type: "default".to_string(),
985                reduced_debug_info: false,
986            }
987        );
988    }
989
990    #[test]
991    fn respawn_roundtrip() {
992        roundtrip!(
993            ClientboundRespawn,
994            ClientboundRespawn {
995                dimension: -1,
996                difficulty: 2,
997                game_mode: 0,
998                level_type: "default".to_string(),
999            }
1000        );
1001    }
1002
1003    #[test]
1004    fn player_position_roundtrip() {
1005        roundtrip!(
1006            ClientboundPlayerPosition,
1007            ClientboundPlayerPosition {
1008                x: 100.0,
1009                head_y: 65.62,
1010                z: -50.0,
1011                yaw: 0.0,
1012                pitch: 0.0,
1013                flags: 0,
1014            }
1015        );
1016    }
1017
1018    #[test]
1019    fn keepalive_roundtrip() {
1020        roundtrip!(
1021            ClientboundKeepAlive,
1022            ClientboundKeepAlive {
1023                keep_alive_id: VarInt(12345)
1024            }
1025        );
1026        roundtrip!(
1027            ServerboundKeepAlive,
1028            ServerboundKeepAlive {
1029                keep_alive_id: VarInt(12345)
1030            }
1031        );
1032    }
1033
1034    #[test]
1035    fn chat_roundtrip() {
1036        roundtrip!(
1037            ClientboundChatMessage,
1038            ClientboundChatMessage {
1039                json_message: r#"{"text":"hello"}"#.to_string(),
1040                position: 0,
1041            }
1042        );
1043        roundtrip!(
1044            ServerboundChatMessage,
1045            ServerboundChatMessage {
1046                message: "/help".to_string()
1047            }
1048        );
1049    }
1050
1051    #[test]
1052    fn move_pos_roundtrip() {
1053        roundtrip!(
1054            ServerboundMovePlayerPos,
1055            ServerboundMovePlayerPos {
1056                x: 1.0,
1057                feet_y: 64.0,
1058                z: -3.5,
1059                on_ground: true,
1060            }
1061        );
1062    }
1063
1064    #[test]
1065    fn move_rot_roundtrip() {
1066        roundtrip!(
1067            ServerboundMovePlayerRot,
1068            ServerboundMovePlayerRot {
1069                yaw: 45.0,
1070                pitch: 0.0,
1071                on_ground: true,
1072            }
1073        );
1074    }
1075
1076    #[test]
1077    fn move_pos_rot_roundtrip() {
1078        roundtrip!(
1079            ServerboundMovePlayerPosRot,
1080            ServerboundMovePlayerPosRot {
1081                x: 10.0,
1082                feet_y: 65.0,
1083                z: 20.0,
1084                yaw: 90.0,
1085                pitch: -30.0,
1086                on_ground: false,
1087            }
1088        );
1089    }
1090
1091    #[test]
1092    fn move_status_only_roundtrip() {
1093        roundtrip!(
1094            ServerboundMovePlayerStatusOnly,
1095            ServerboundMovePlayerStatusOnly { on_ground: true }
1096        );
1097    }
1098
1099    #[test]
1100    fn interact_roundtrip() {
1101        roundtrip!(
1102            ServerboundInteract,
1103            ServerboundInteract {
1104                entity_id: VarInt(42),
1105                action: InteractAction::Attack,
1106            }
1107        );
1108        roundtrip!(
1109            ServerboundInteract,
1110            ServerboundInteract {
1111                entity_id: VarInt(7),
1112                action: InteractAction::Interact,
1113            }
1114        );
1115        roundtrip!(
1116            ServerboundInteract,
1117            ServerboundInteract {
1118                entity_id: VarInt(5),
1119                action: InteractAction::InteractAt {
1120                    target_x: 0.5,
1121                    target_y: 1.0,
1122                    target_z: 0.5
1123                },
1124            }
1125        );
1126    }
1127
1128    #[test]
1129    fn plugin_message_roundtrip() {
1130        roundtrip!(
1131            ClientboundPluginMessage,
1132            ClientboundPluginMessage {
1133                channel: "MC|Brand".to_string(),
1134                data: b"vanilla".to_vec(),
1135            }
1136        );
1137        roundtrip!(
1138            ServerboundPluginMessage,
1139            ServerboundPluginMessage {
1140                channel: "MC|Brand".to_string(),
1141                data: b"vanilla".to_vec(),
1142            }
1143        );
1144    }
1145
1146    #[test]
1147    fn disconnect_roundtrip() {
1148        roundtrip!(
1149            ClientboundDisconnect,
1150            ClientboundDisconnect {
1151                reason: r#"{"text":"You are banned"}"#.to_string(),
1152            }
1153        );
1154    }
1155
1156    #[test]
1157    fn set_held_item_roundtrip() {
1158        roundtrip!(ClientboundSetHeldItem, ClientboundSetHeldItem { slot: 3 });
1159    }
1160
1161    #[test]
1162    fn animation_roundtrip() {
1163        roundtrip!(ServerboundAnimation, ServerboundAnimation);
1164    }
1165
1166    #[test]
1167    fn packet_ids() {
1168        assert_eq!(ClientboundJoinGame::packet_id(47), 0x01);
1169        assert_eq!(ClientboundLoginPlay::packet_id(47), 0x01);
1170        assert_eq!(ClientboundRespawn::packet_id(47), 0x07);
1171        assert_eq!(ClientboundPlayerPosition::packet_id(47), 0x08);
1172        assert_eq!(ClientboundKeepAlive::packet_id(47), 0x00);
1173        assert_eq!(ServerboundKeepAlive::packet_id(47), 0x00);
1174        assert_eq!(ClientboundChatMessage::packet_id(47), 0x02);
1175        assert_eq!(ServerboundChatMessage::packet_id(47), 0x01);
1176        assert_eq!(ServerboundMovePlayerPos::packet_id(47), 0x04);
1177        assert_eq!(ServerboundMovePlayerRot::packet_id(47), 0x05);
1178        assert_eq!(ServerboundMovePlayerPosRot::packet_id(47), 0x06);
1179        assert_eq!(ServerboundMovePlayerStatusOnly::packet_id(47), 0x03);
1180        assert_eq!(ServerboundInteract::packet_id(47), 0x02);
1181        assert_eq!(ClientboundPluginMessage::packet_id(47), 0x3F);
1182        assert_eq!(ServerboundPluginMessage::packet_id(47), 0x17);
1183        assert_eq!(ClientboundDisconnect::packet_id(47), 0x40);
1184        assert_eq!(ClientboundSetHeldItem::packet_id(47), 0x09);
1185        assert_eq!(ServerboundAnimation::packet_id(47), 0x0A);
1186        assert_eq!(ServerboundPlayerBlockPlacement::packet_id(47), 0x08);
1187        assert_eq!(ServerboundClientSettings::packet_id(47), 0x15);
1188    }
1189}