Skip to main content

steel_protocol/packets/game/
c_commands.rs

1use std::borrow::Cow;
2use std::io::{Result, Write};
3
4use steel_macros::{ClientPacket, WriteTo};
5use steel_registry::packets::play::C_COMMANDS;
6use steel_utils::{
7    codec::VarInt,
8    serial::{PrefixedWrite, WriteTo},
9};
10
11#[derive(ClientPacket, WriteTo)]
12#[packet_id(Play = C_COMMANDS)]
13pub struct CCommands {
14    pub nodes: Vec<CommandNode>,
15    #[write(as = VarInt)]
16    pub root_index: i32,
17}
18
19pub enum CommandNode {
20    Root {
21        children: Vec<i32>,
22    },
23    Literal {
24        children: Vec<i32>,
25        redirects_to: Option<i32>,
26        name: Cow<'static, str>,
27        is_executable: bool,
28        is_restricted: bool,
29    },
30    Argument {
31        children: Vec<i32>,
32        redirects_to: Option<i32>,
33        name: Cow<'static, str>,
34        is_executable: bool,
35        is_restricted: bool,
36        parser: ArgumentType,
37        suggestions_type: Option<SuggestionType>,
38    },
39}
40
41impl CommandNode {
42    const FLAG_IS_EXECUTABLE: u8 = 4;
43    const FLAG_HAS_REDIRECT: u8 = 8;
44    const FLAG_HAS_SUGGESTION_TYPE: u8 = 16;
45    const FLAG_IS_RESTRICTED: u8 = 32;
46
47    #[must_use]
48    pub const fn new_root() -> Self {
49        Self::Root {
50            children: Vec::new(),
51        }
52    }
53
54    pub fn new_literal(info: CommandNodeInfo, name: impl Into<Cow<'static, str>>) -> Self {
55        Self::Literal {
56            children: info.children,
57            name: name.into(),
58            is_executable: info.is_executable,
59            is_restricted: info.is_restricted,
60            redirects_to: info.redirects_to,
61        }
62    }
63
64    pub fn new_argument(
65        info: CommandNodeInfo,
66        name: impl Into<Cow<'static, str>>,
67        argument: (ArgumentType, Option<SuggestionType>),
68    ) -> Self {
69        Self::Argument {
70            children: info.children,
71            name: name.into(),
72            is_executable: info.is_executable,
73            is_restricted: info.is_restricted,
74            redirects_to: info.redirects_to,
75            parser: argument.0,
76            suggestions_type: argument.1,
77        }
78    }
79
80    const fn flags(&self) -> u8 {
81        let (mut flags, is_executable, has_redirect, has_suggestions_type, is_restricted) =
82            match self {
83                CommandNode::Root { .. } => (0, false, false, false, false),
84                CommandNode::Literal {
85                    is_executable,
86                    redirects_to,
87                    is_restricted,
88                    ..
89                } => (
90                    1,
91                    *is_executable,
92                    redirects_to.is_some(),
93                    false,
94                    *is_restricted,
95                ),
96                CommandNode::Argument {
97                    is_executable,
98                    redirects_to: r,
99                    suggestions_type,
100                    is_restricted,
101                    ..
102                } => (
103                    2,
104                    *is_executable,
105                    r.is_some(),
106                    suggestions_type.is_some(),
107                    *is_restricted,
108                ),
109            };
110
111        if is_executable {
112            flags |= Self::FLAG_IS_EXECUTABLE;
113        }
114        if has_redirect {
115            flags |= Self::FLAG_HAS_REDIRECT;
116        }
117        if has_suggestions_type {
118            flags |= Self::FLAG_HAS_SUGGESTION_TYPE;
119        }
120        if is_restricted {
121            flags |= Self::FLAG_IS_RESTRICTED;
122        }
123        flags
124    }
125
126    pub fn set_children(&mut self, children: Vec<i32>) {
127        match self {
128            CommandNode::Root { children: c } => *c = children,
129            CommandNode::Literal { children: c, .. } => *c = children,
130            CommandNode::Argument { children: c, .. } => *c = children,
131        }
132    }
133
134    fn children(&self) -> &[i32] {
135        match self {
136            CommandNode::Root { children } => children,
137            CommandNode::Literal { children, .. } => children,
138            CommandNode::Argument { children, .. } => children,
139        }
140    }
141
142    const fn redirects_to(&self) -> &Option<i32> {
143        match self {
144            CommandNode::Root { .. } => &None,
145            CommandNode::Literal { redirects_to, .. } => redirects_to,
146            CommandNode::Argument { redirects_to, .. } => redirects_to,
147        }
148    }
149
150    fn name(&self) -> Option<&str> {
151        match self {
152            CommandNode::Root { .. } => None,
153            CommandNode::Literal { name, .. } => Some(name),
154            CommandNode::Argument { name, .. } => Some(name),
155        }
156    }
157}
158
159impl WriteTo for CommandNode {
160    fn write(&self, writer: &mut impl Write) -> Result<()> {
161        writer.write_all(&self.flags().to_be_bytes())?;
162        let children = self.children();
163        VarInt(children.len() as i32).write(writer)?;
164        for child in children {
165            VarInt(*child).write(writer)?;
166        }
167
168        if let Some(redirects_to) = self.redirects_to() {
169            VarInt(*redirects_to).write(writer)?;
170        }
171
172        if let Some(name) = self.name() {
173            name.write_prefixed::<VarInt>(writer)?;
174        }
175
176        if let CommandNode::Argument {
177            parser,
178            suggestions_type,
179            ..
180        } = self
181        {
182            parser.write(writer)?;
183
184            if let Some(suggestions_type) = suggestions_type {
185                suggestions_type.as_str().write_prefixed::<VarInt>(writer)?;
186            }
187        }
188
189        Ok(())
190    }
191}
192
193pub struct CommandNodeInfo {
194    children: Vec<i32>,
195    is_executable: bool,
196    is_restricted: bool,
197    redirects_to: Option<i32>,
198}
199
200impl CommandNodeInfo {
201    #[must_use]
202    pub const fn new(children: Vec<i32>) -> Self {
203        Self {
204            children,
205            is_executable: false,
206            is_restricted: false,
207            redirects_to: None,
208        }
209    }
210
211    #[must_use]
212    pub const fn new_executable() -> Self {
213        Self {
214            children: Vec::new(),
215            is_executable: true,
216            is_restricted: false,
217            redirects_to: None,
218        }
219    }
220
221    #[must_use]
222    pub const fn new_redirect(redirects_to: i32) -> Self {
223        Self {
224            children: Vec::new(),
225            is_executable: false,
226            is_restricted: false,
227            redirects_to: Some(redirects_to),
228        }
229    }
230
231    /// Marks this node as executable.
232    #[must_use]
233    pub const fn executable(mut self) -> Self {
234        self.is_executable = true;
235        self
236    }
237
238    /// Marks this node as requiring authorization on the server.
239    #[must_use]
240    pub const fn restricted(mut self) -> Self {
241        self.is_restricted = true;
242        self
243    }
244
245    /// Adds a redirect to another serialized command node.
246    #[must_use]
247    pub const fn redirect(mut self, redirects_to: i32) -> Self {
248        self.redirects_to = Some(redirects_to);
249        self
250    }
251
252    #[must_use]
253    pub fn chain(mut self, mut other: Self) -> Self {
254        self.children.append(&mut other.children);
255        self.is_executable |= other.is_executable;
256        self.is_restricted |= other.is_restricted;
257        self
258    }
259}
260
261pub enum ArgumentType {
262    Bool,
263    Float {
264        min: Option<f32>,
265        max: Option<f32>,
266    },
267    Double {
268        min: Option<f64>,
269        max: Option<f64>,
270    },
271    Integer {
272        min: Option<i32>,
273        max: Option<i32>,
274    },
275    Long {
276        min: Option<i64>,
277        max: Option<i64>,
278    },
279    String {
280        behavior: ArgumentStringTypeBehavior,
281    },
282    Entity {
283        flags: u8,
284    },
285    GameProfile,
286    BlockPos,
287    ColumnPos,
288    Vec3,
289    Vec2,
290    BlockState,
291    BlockPredicate,
292    ItemStack,
293    ItemPredicate,
294    Color,
295    HexColor,
296    Component,
297    Style,
298    Message,
299    Nbt,
300    NbtTag,
301    NbtPath,
302    Objective,
303    ObjectiveCriteria,
304    Operation,
305    Particle,
306    Angle,
307    Rotation,
308    ScoreboardSlot,
309    ScoreHolder {
310        flags: u8,
311    },
312    Swizzle,
313    Team,
314    ItemSlot,
315    ItemSlots,
316    ResourceLocation,
317    Function,
318    EntityAnchor,
319    IntRange,
320    FloatRange,
321    Dimension,
322    Gamemode,
323    Time {
324        min: i32,
325    },
326    ResourceOrTag {
327        identifier: &'static str,
328    },
329    ResourceOrTagKey {
330        identifier: &'static str,
331    },
332    Resource {
333        identifier: &'static str,
334    },
335    ResourceKey {
336        identifier: &'static str,
337    },
338    ResourceSelector {
339        identifier: &'static str,
340    },
341    TemplateMirror,
342    TemplateRotation,
343    Heightmap,
344    LootTable,
345    LootPredicate,
346    LootModifier,
347    Dialog,
348    Uuid,
349}
350
351#[derive(Debug, Clone, Copy)]
352pub enum ArgumentStringTypeBehavior {
353    SingleWord,
354    QuotablePhrase,
355    GreedyPhrase,
356}
357
358impl ArgumentType {
359    const fn discriminant(&self) -> i32 {
360        match self {
361            Self::Bool => 0,
362            Self::Float { .. } => 1,
363            Self::Double { .. } => 2,
364            Self::Integer { .. } => 3,
365            Self::Long { .. } => 4,
366            Self::String { .. } => 5,
367            Self::Entity { .. } => 6,
368            Self::GameProfile => 7,
369            Self::BlockPos => 8,
370            Self::ColumnPos => 9,
371            Self::Vec3 => 10,
372            Self::Vec2 => 11,
373            Self::BlockState => 12,
374            Self::BlockPredicate => 13,
375            Self::ItemStack => 14,
376            Self::ItemPredicate => 15,
377            Self::Color => 16,
378            Self::HexColor => 17,
379            Self::Component => 18,
380            Self::Style => 19,
381            Self::Message => 20,
382            Self::Nbt => 21,
383            Self::NbtTag => 22,
384            Self::NbtPath => 23,
385            Self::Objective => 24,
386            Self::ObjectiveCriteria => 25,
387            Self::Operation => 26,
388            Self::Particle => 27,
389            Self::Angle => 28,
390            Self::Rotation => 29,
391            Self::ScoreboardSlot => 30,
392            Self::ScoreHolder { .. } => 31,
393            Self::Swizzle => 32,
394            Self::Team => 33,
395            Self::ItemSlot => 34,
396            Self::ItemSlots => 35,
397            Self::ResourceLocation => 36,
398            Self::Function => 37,
399            Self::EntityAnchor => 38,
400            Self::IntRange => 39,
401            Self::FloatRange => 40,
402            Self::Dimension => 41,
403            Self::Gamemode => 42,
404            Self::Time { .. } => 43,
405            Self::ResourceOrTag { .. } => 44,
406            Self::ResourceOrTagKey { .. } => 45,
407            Self::Resource { .. } => 46,
408            Self::ResourceKey { .. } => 47,
409            Self::ResourceSelector { .. } => 48,
410            Self::TemplateMirror => 49,
411            Self::TemplateRotation => 50,
412            Self::Heightmap => 51,
413            Self::LootTable => 52,
414            Self::LootPredicate => 53,
415            Self::LootModifier => 54,
416            Self::Dialog => 55,
417            Self::Uuid => 56,
418        }
419    }
420}
421
422impl WriteTo for ArgumentType {
423    fn write(&self, writer: &mut impl Write) -> Result<()> {
424        VarInt(self.discriminant()).write(writer)?;
425
426        match self {
427            Self::Float { min, max } => Self::write_min_max(*min, *max, writer),
428            Self::Double { min, max } => Self::write_min_max(*min, *max, writer),
429            Self::Integer { min, max } => Self::write_min_max(*min, *max, writer),
430            Self::Long { min, max } => Self::write_min_max(*min, *max, writer),
431            Self::String { behavior } => {
432                let i = match behavior {
433                    ArgumentStringTypeBehavior::SingleWord => 0,
434                    ArgumentStringTypeBehavior::QuotablePhrase => 1,
435                    ArgumentStringTypeBehavior::GreedyPhrase => 2,
436                };
437                VarInt(i).write(writer)
438            }
439            Self::Entity { flags } => flags.write(writer),
440            Self::ScoreHolder { flags } => flags.write(writer),
441            Self::Time { min } => min.write(writer),
442            Self::ResourceOrTag { identifier } => identifier.write_prefixed::<VarInt>(writer),
443            Self::ResourceOrTagKey { identifier } => identifier.write_prefixed::<VarInt>(writer),
444            Self::Resource { identifier } => identifier.write_prefixed::<VarInt>(writer),
445            Self::ResourceKey { identifier } => identifier.write_prefixed::<VarInt>(writer),
446            Self::ResourceSelector { identifier } => identifier.write_prefixed::<VarInt>(writer),
447            _ => Ok(()),
448        }
449    }
450}
451
452impl ArgumentType {
453    fn write_min_max<T: WriteTo>(
454        min: Option<T>,
455        max: Option<T>,
456        writer: &mut impl Write,
457    ) -> Result<()> {
458        // none = 0
459        // min = 1
460        // max = 2
461        // min & max = 3
462        (u8::from(min.is_some()) + u8::from(max.is_some()) + u8::from(max.is_some()))
463            .write(writer)?;
464
465        if let Some(min) = min {
466            min.write(writer)?;
467        }
468        if let Some(max) = max {
469            max.write(writer)?;
470        }
471
472        Ok(())
473    }
474}
475
476pub enum SuggestionType {
477    AskServer,
478    AllRecipes,
479    AvailableSounds,
480    SummonableEntities,
481}
482
483impl SuggestionType {
484    const fn as_str(&self) -> &str {
485        match self {
486            SuggestionType::AskServer => "minecraft:ask_server",
487            SuggestionType::AllRecipes => "minecraft:all_recipes",
488            SuggestionType::AvailableSounds => "minecraft:available_sounds",
489            SuggestionType::SummonableEntities => "minecraft:summonable_entities",
490        }
491    }
492}
493
494#[cfg(test)]
495mod tests {
496    use steel_utils::serial::WriteTo;
497
498    use super::{ArgumentType, CommandNode, CommandNodeInfo};
499
500    #[test]
501    fn restricted_nodes_set_the_26_2_protocol_flag() {
502        let node = CommandNode::new_literal(CommandNodeInfo::new(Vec::new()).restricted(), "op");
503        let mut encoded = Vec::new();
504
505        assert!(node.write(&mut encoded).is_ok());
506        assert_eq!(encoded.first().copied(), Some(1 | 32));
507    }
508
509    #[test]
510    fn command_node_flags_can_be_combined() {
511        let info = CommandNodeInfo::new(Vec::new())
512            .executable()
513            .restricted()
514            .redirect(0);
515        let node = CommandNode::new_literal(info, "alias");
516        let mut encoded = Vec::new();
517
518        assert!(node.write(&mut encoded).is_ok());
519        assert_eq!(encoded.first().copied(), Some(1 | 4 | 8 | 32));
520    }
521
522    #[test]
523    fn command_argument_tail_uses_the_26_2_registry_ids() {
524        for (argument, expected) in [
525            (ArgumentType::TemplateMirror, 49),
526            (ArgumentType::TemplateRotation, 50),
527            (ArgumentType::Heightmap, 51),
528            (ArgumentType::LootTable, 52),
529            (ArgumentType::LootPredicate, 53),
530            (ArgumentType::LootModifier, 54),
531            (ArgumentType::Dialog, 55),
532            (ArgumentType::Uuid, 56),
533        ] {
534            let mut encoded = Vec::new();
535            assert!(argument.write(&mut encoded).is_ok());
536            assert_eq!(encoded, [expected]);
537        }
538    }
539
540    #[test]
541    fn resource_selector_writes_its_registry_key() {
542        let mut encoded = Vec::new();
543        let argument = ArgumentType::ResourceSelector {
544            identifier: "minecraft:test_instance",
545        };
546
547        assert!(argument.write(&mut encoded).is_ok());
548        assert_eq!(&encoded[..2], [48, 23]);
549        assert_eq!(&encoded[2..], b"minecraft:test_instance");
550    }
551}