chunkedge_protocol/packets/play/
commands_s2c.rs

1use std::borrow::Cow;
2use std::io::Write;
3
4use anyhow::bail;
5use byteorder::WriteBytesExt;
6use chunkedge_binary::{Decode, Encode, VarInt};
7use chunkedge_ident::Ident;
8
9use crate::Packet;
10
11#[derive(Clone, Debug, Encode, Decode, Packet)]
12pub struct CommandsS2c<'a> {
13    pub commands: Vec<Node<'a>>,
14    pub root_index: VarInt,
15}
16
17#[derive(Clone, Debug)]
18pub struct Node<'a> {
19    pub data: NodeData<'a>,
20    pub executable: bool,
21    pub children: Vec<VarInt>,
22    pub redirect_node: Option<VarInt>,
23    /// Set if the node requires the player to have a permission level above 0.
24    pub is_restricted: bool,
25}
26
27#[derive(Clone, Debug, PartialEq)]
28pub enum NodeData<'a> {
29    Root,
30    Literal {
31        name: Cow<'a, str>,
32    },
33    Argument {
34        name: Cow<'a, str>,
35        parser: Parser<'a>,
36        suggestion: Option<Suggestion>,
37    },
38}
39
40#[derive(Copy, Clone, PartialEq, Eq, Debug)]
41pub enum Suggestion {
42    AskServer,
43    AllRecipes,
44    AvailableSounds,
45    SummonableEntities,
46}
47
48#[derive(Clone, Debug, PartialEq)]
49pub enum Parser<'a> {
50    Bool,
51    Float { min: Option<f32>, max: Option<f32> },
52    Double { min: Option<f64>, max: Option<f64> },
53    Integer { min: Option<i32>, max: Option<i32> },
54    Long { min: Option<i64>, max: Option<i64> },
55    String(StringArg),
56    Entity { single: bool, only_players: bool },
57    GameProfile,
58    BlockPos,
59    ColumnPos,
60    Vec3,
61    Vec2,
62    BlockState,
63    BlockPredicate,
64    ItemStack,
65    ItemPredicate,
66    Color,
67    Component,
68    Style,
69    Message,
70    NbtCompoundTag,
71    NbtTag,
72    NbtPath,
73    Objective,
74    ObjectiveCriteria,
75    Operation,
76    Particle,
77    Angle,
78    Rotation,
79    ScoreboardSlot,
80    ScoreHolder { allow_multiple: bool },
81    Swizzle,
82    Team,
83    ItemSlot,
84    ItemSlots,
85    ResourceLocation,
86    Function,
87    EntityAnchor,
88    IntRange,
89    FloatRange,
90    Dimension,
91    GameMode,
92    Time { min: i32 },
93    ResourceOrTag { registry: Ident<Cow<'a, str>> },
94    ResourceOrTagKey { registry: Ident<Cow<'a, str>> },
95    Resource { registry: Ident<Cow<'a, str>> },
96    ResourceKey { registry: Ident<Cow<'a, str>> },
97    ResourceSelector { registry: Ident<Cow<'a, str>> },
98    TemplateMirror,
99    TemplateRotation,
100    Heightmap,
101    LootTable,
102    LootPredicate,
103    LootModifier,
104    Dialog,
105    Uuid,
106}
107
108#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
109pub enum StringArg {
110    SingleWord,
111    QuotablePhrase,
112    GreedyPhrase,
113}
114
115impl Encode for Node<'_> {
116    fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
117        let node_type = match &self.data {
118            NodeData::Root => 0,
119            NodeData::Literal { .. } => 1,
120            NodeData::Argument { .. } => 2,
121        };
122
123        let has_suggestion = matches!(
124            &self.data,
125            NodeData::Argument {
126                suggestion: Some(_),
127                ..
128            }
129        );
130
131        let flags: u8 = node_type
132            | (u8::from(self.executable) * 0x04)
133            | (u8::from(self.redirect_node.is_some()) * 0x08)
134            | (u8::from(has_suggestion) * 0x10)
135            | (u8::from(self.is_restricted) * 0x20);
136
137        w.write_u8(flags)?;
138
139        self.children.encode(&mut w)?;
140
141        if let Some(redirect_node) = self.redirect_node {
142            redirect_node.encode(&mut w)?;
143        }
144
145        match &self.data {
146            NodeData::Root => {}
147            NodeData::Literal { name } => {
148                name.encode(&mut w)?;
149            }
150            NodeData::Argument {
151                name,
152                parser,
153                suggestion,
154            } => {
155                name.encode(&mut w)?;
156                parser.encode(&mut w)?;
157
158                if let Some(suggestion) = suggestion {
159                    match suggestion {
160                        Suggestion::AskServer => "minecraft:ask_server",
161                        Suggestion::AllRecipes => "minecraft:all_recipes",
162                        Suggestion::AvailableSounds => "minecraft:available_sounds",
163                        Suggestion::SummonableEntities => "minecraft:summonable_entities",
164                    }
165                    .encode(&mut w)?;
166                }
167            }
168        }
169
170        Ok(())
171    }
172}
173
174impl<'a> Decode<'a> for Node<'a> {
175    fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
176        let flags = u8::decode(r)?;
177
178        let children = Vec::decode(r)?;
179
180        let redirect_node = if flags & 0x08 != 0 {
181            Some(VarInt::decode(r)?)
182        } else {
183            None
184        };
185
186        let node_data = match flags & 0x3 {
187            0 => NodeData::Root,
188            1 => NodeData::Literal {
189                name: <Cow<'a, str>>::decode(r)?,
190            },
191            2 => NodeData::Argument {
192                name: <Cow<'a, str>>::decode(r)?,
193                parser: Parser::decode(r)?,
194                suggestion: if flags & 0x10 != 0 {
195                    Some(match Ident::<Cow<str>>::decode(r)?.as_str() {
196                        "minecraft:ask_server" => Suggestion::AskServer,
197                        "minecraft:all_recipes" => Suggestion::AllRecipes,
198                        "minecraft:available_sounds" => Suggestion::AvailableSounds,
199                        "minecraft:summonable_entities" => Suggestion::SummonableEntities,
200                        other => bail!("unknown command suggestion type of \"{other}\""),
201                    })
202                } else {
203                    None
204                },
205            },
206            n => bail!("invalid node type of {n}"),
207        };
208
209        Ok(Self {
210            children,
211            data: node_data,
212            executable: flags & 0x04 != 0,
213            redirect_node,
214            is_restricted: flags & 0x20 != 0,
215        })
216    }
217}
218
219impl Encode for Parser<'_> {
220    fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
221        match self {
222            Parser::Bool => 0_u8.encode(&mut w)?,
223            Parser::Float { min, max } => {
224                1_u8.encode(&mut w)?;
225
226                (u8::from(min.is_some()) | (u8::from(max.is_some()) * 0x2)).encode(&mut w)?;
227
228                if let Some(min) = min {
229                    min.encode(&mut w)?;
230                }
231
232                if let Some(max) = max {
233                    max.encode(&mut w)?;
234                }
235            }
236            Parser::Double { min, max } => {
237                2_u8.encode(&mut w)?;
238
239                (u8::from(min.is_some()) | (u8::from(max.is_some()) * 0x2)).encode(&mut w)?;
240
241                if let Some(min) = min {
242                    min.encode(&mut w)?;
243                }
244
245                if let Some(max) = max {
246                    max.encode(&mut w)?;
247                }
248            }
249            Parser::Integer { min, max } => {
250                3_u8.encode(&mut w)?;
251
252                (u8::from(min.is_some()) | (u8::from(max.is_some()) * 0x2)).encode(&mut w)?;
253
254                if let Some(min) = min {
255                    min.encode(&mut w)?;
256                }
257
258                if let Some(max) = max {
259                    max.encode(&mut w)?;
260                }
261            }
262            Parser::Long { min, max } => {
263                4_u8.encode(&mut w)?;
264
265                (u8::from(min.is_some()) | (u8::from(max.is_some()) * 0x2)).encode(&mut w)?;
266
267                if let Some(min) = min {
268                    min.encode(&mut w)?;
269                }
270
271                if let Some(max) = max {
272                    max.encode(&mut w)?;
273                }
274            }
275            Parser::String(arg) => {
276                5_u8.encode(&mut w)?;
277                arg.encode(&mut w)?;
278            }
279            Parser::Entity {
280                single,
281                only_players,
282            } => {
283                6_u8.encode(&mut w)?;
284                (u8::from(*single) | (u8::from(*only_players) * 0x2)).encode(&mut w)?;
285            }
286            Parser::GameProfile => 7_u8.encode(&mut w)?,
287            Parser::BlockPos => 8_u8.encode(&mut w)?,
288            Parser::ColumnPos => 9_u8.encode(&mut w)?,
289            Parser::Vec3 => 10_u8.encode(&mut w)?,
290            Parser::Vec2 => 11_u8.encode(&mut w)?,
291            Parser::BlockState => 12_u8.encode(&mut w)?,
292            Parser::BlockPredicate => 13_u8.encode(&mut w)?,
293            Parser::ItemStack => 14_u8.encode(&mut w)?,
294            Parser::ItemPredicate => 15_u8.encode(&mut w)?,
295            Parser::Color => 16_u8.encode(&mut w)?,
296            Parser::Component => 17_u8.encode(&mut w)?,
297            Parser::Style => 18_u8.encode(&mut w)?,
298            Parser::Message => 19_u8.encode(&mut w)?,
299            Parser::NbtCompoundTag => 20_u8.encode(&mut w)?,
300            Parser::NbtTag => 21_u8.encode(&mut w)?,
301            Parser::NbtPath => 22_u8.encode(&mut w)?,
302            Parser::Objective => 23_u8.encode(&mut w)?,
303            Parser::ObjectiveCriteria => 24_u8.encode(&mut w)?,
304            Parser::Operation => 25_u8.encode(&mut w)?,
305            Parser::Particle => 26_u8.encode(&mut w)?,
306            Parser::Angle => 27_u8.encode(&mut w)?,
307            Parser::Rotation => 28_u8.encode(&mut w)?,
308            Parser::ScoreboardSlot => 29_u8.encode(&mut w)?,
309            Parser::ScoreHolder { allow_multiple } => {
310                30_u8.encode(&mut w)?;
311                allow_multiple.encode(&mut w)?;
312            }
313            Parser::Swizzle => 31_u8.encode(&mut w)?,
314            Parser::Team => 32_u8.encode(&mut w)?,
315            Parser::ItemSlot => 33_u8.encode(&mut w)?,
316            Parser::ItemSlots => 34_u8.encode(&mut w)?,
317            Parser::ResourceLocation => 35_u8.encode(&mut w)?,
318            Parser::Function => 36_u8.encode(&mut w)?,
319            Parser::EntityAnchor => 37_u8.encode(&mut w)?,
320            Parser::IntRange => 38_u8.encode(&mut w)?,
321            Parser::FloatRange => 39_u8.encode(&mut w)?,
322            Parser::Dimension => 40_u8.encode(&mut w)?,
323            Parser::GameMode => 41_u8.encode(&mut w)?,
324            Parser::Time { min } => {
325                42_u8.encode(&mut w)?;
326                min.encode(&mut w)?;
327            }
328            Parser::ResourceOrTag { registry } => {
329                43_u8.encode(&mut w)?;
330                registry.encode(&mut w)?;
331            }
332            Parser::ResourceOrTagKey { registry } => {
333                44_u8.encode(&mut w)?;
334                registry.encode(&mut w)?;
335            }
336            Parser::Resource { registry } => {
337                45_u8.encode(&mut w)?;
338                registry.encode(&mut w)?;
339            }
340            Parser::ResourceKey { registry } => {
341                46_u8.encode(&mut w)?;
342                registry.encode(&mut w)?;
343            }
344            Parser::ResourceSelector { registry } => {
345                47_u8.encode(&mut w)?;
346                registry.encode(&mut w)?;
347            }
348            Parser::TemplateMirror => 48_u8.encode(&mut w)?,
349            Parser::TemplateRotation => 49_u8.encode(&mut w)?,
350            Parser::Heightmap => 50_u8.encode(&mut w)?,
351            Parser::LootTable => 51_u8.encode(&mut w)?,
352            Parser::LootPredicate => 52_u8.encode(&mut w)?,
353            Parser::LootModifier => 53_u8.encode(&mut w)?,
354            Parser::Dialog => 55_u8.encode(&mut w)?,
355            Parser::Uuid => 56_u8.encode(&mut w)?,
356        }
357
358        Ok(())
359    }
360}
361
362impl<'a> Decode<'a> for Parser<'a> {
363    fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
364        fn decode_min_max<'a, T: Decode<'a>>(
365            r: &mut &'a [u8],
366        ) -> anyhow::Result<(Option<T>, Option<T>)> {
367            let flags = u8::decode(r)?;
368
369            let min = if flags & 0x1 != 0 {
370                Some(T::decode(r)?)
371            } else {
372                None
373            };
374
375            let max = if flags & 0x2 != 0 {
376                Some(T::decode(r)?)
377            } else {
378                None
379            };
380
381            Ok((min, max))
382        }
383
384        Ok(match u8::decode(r)? {
385            0 => Self::Bool,
386            1 => {
387                let (min, max) = decode_min_max(r)?;
388                Self::Float { min, max }
389            }
390            2 => {
391                let (min, max) = decode_min_max(r)?;
392                Self::Double { min, max }
393            }
394            3 => {
395                let (min, max) = decode_min_max(r)?;
396                Self::Integer { min, max }
397            }
398            4 => {
399                let (min, max) = decode_min_max(r)?;
400                Self::Long { min, max }
401            }
402            5 => Self::String(StringArg::decode(r)?),
403            6 => {
404                let flags = u8::decode(r)?;
405                Self::Entity {
406                    single: flags & 0x1 != 0,
407                    only_players: flags & 0x2 != 0,
408                }
409            }
410            7 => Self::GameProfile,
411            8 => Self::BlockPos,
412            9 => Self::ColumnPos,
413            10 => Self::Vec3,
414            11 => Self::Vec2,
415            12 => Self::BlockState,
416            13 => Self::BlockPredicate,
417            14 => Self::ItemStack,
418            15 => Self::ItemPredicate,
419            16 => Self::Color,
420            17 => Self::Component,
421            18 => Self::Style,
422            19 => Self::Message,
423            20 => Self::NbtCompoundTag,
424            21 => Self::NbtTag,
425            22 => Self::NbtPath,
426            23 => Self::Objective,
427            24 => Self::ObjectiveCriteria,
428            25 => Self::Operation,
429            26 => Self::Particle,
430            27 => Self::Angle,
431            28 => Self::Rotation,
432            29 => Self::ScoreboardSlot,
433            30 => Self::ScoreHolder {
434                allow_multiple: bool::decode(r)?,
435            },
436            31 => Self::Swizzle,
437            32 => Self::Team,
438            33 => Self::ItemSlot,
439            34 => Self::ItemSlots,
440            35 => Self::ResourceLocation,
441            36 => Self::Function,
442            37 => Self::EntityAnchor,
443            38 => Self::IntRange,
444            39 => Self::FloatRange,
445            40 => Self::Dimension,
446            41 => Self::GameMode,
447            42 => Self::Time {
448                min: i32::decode(r)?,
449            },
450            43 => Self::ResourceOrTag {
451                registry: Ident::decode(r)?,
452            },
453            44 => Self::ResourceOrTagKey {
454                registry: Ident::decode(r)?,
455            },
456            45 => Self::Resource {
457                registry: Ident::decode(r)?,
458            },
459            46 => Self::ResourceKey {
460                registry: Ident::decode(r)?,
461            },
462            47 => Self::ResourceSelector {
463                registry: Ident::decode(r)?,
464            },
465            48 => Self::TemplateMirror,
466            49 => Self::TemplateRotation,
467            50 => Self::Heightmap,
468            51 => Self::LootTable,
469            52 => Self::LootPredicate,
470            53 => Self::LootModifier,
471            55 => Self::Dialog,
472            56 => Self::Uuid,
473            n => bail!("unknown command parser ID of {n}"),
474        })
475    }
476}