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    SwingAnimation,
349    Uuid,
350}
351
352#[derive(Debug, Clone, Copy)]
353pub enum ArgumentStringTypeBehavior {
354    SingleWord,
355    QuotablePhrase,
356    GreedyPhrase,
357}
358
359impl ArgumentType {
360    const fn discriminant(&self) -> i32 {
361        match self {
362            Self::Bool => 0,
363            Self::Float { .. } => 1,
364            Self::Double { .. } => 2,
365            Self::Integer { .. } => 3,
366            Self::Long { .. } => 4,
367            Self::String { .. } => 5,
368            Self::Entity { .. } => 6,
369            Self::GameProfile => 7,
370            Self::BlockPos => 8,
371            Self::ColumnPos => 9,
372            Self::Vec3 => 10,
373            Self::Vec2 => 11,
374            Self::BlockState => 12,
375            Self::BlockPredicate => 13,
376            Self::ItemStack => 14,
377            Self::ItemPredicate => 15,
378            Self::Color => 16,
379            Self::HexColor => 17,
380            Self::Component => 18,
381            Self::Style => 19,
382            Self::Message => 20,
383            Self::Nbt => 21,
384            Self::NbtTag => 22,
385            Self::NbtPath => 23,
386            Self::Objective => 24,
387            Self::ObjectiveCriteria => 25,
388            Self::Operation => 26,
389            Self::Particle => 27,
390            Self::Angle => 28,
391            Self::Rotation => 29,
392            Self::ScoreboardSlot => 30,
393            Self::ScoreHolder { .. } => 31,
394            Self::Swizzle => 32,
395            Self::Team => 33,
396            Self::ItemSlot => 34,
397            Self::ItemSlots => 35,
398            Self::ResourceLocation => 36,
399            Self::Function => 37,
400            Self::EntityAnchor => 38,
401            Self::IntRange => 39,
402            Self::FloatRange => 40,
403            Self::Dimension => 41,
404            Self::Gamemode => 42,
405            Self::Time { .. } => 43,
406            Self::ResourceOrTag { .. } => 44,
407            Self::ResourceOrTagKey { .. } => 45,
408            Self::Resource { .. } => 46,
409            Self::ResourceKey { .. } => 47,
410            Self::ResourceSelector { .. } => 48,
411            Self::TemplateMirror => 49,
412            Self::TemplateRotation => 50,
413            Self::Heightmap => 51,
414            Self::LootTable => 52,
415            Self::LootPredicate => 53,
416            Self::LootModifier => 54,
417            Self::Dialog => 58,
418            Self::SwingAnimation => 60,
419            Self::Uuid => 61,
420        }
421    }
422}
423
424impl WriteTo for ArgumentType {
425    fn write(&self, writer: &mut impl Write) -> Result<()> {
426        VarInt(self.discriminant()).write(writer)?;
427
428        match self {
429            Self::Float { min, max } => Self::write_min_max(*min, *max, writer),
430            Self::Double { min, max } => Self::write_min_max(*min, *max, writer),
431            Self::Integer { min, max } => Self::write_min_max(*min, *max, writer),
432            Self::Long { min, max } => Self::write_min_max(*min, *max, writer),
433            Self::String { behavior } => {
434                let i = match behavior {
435                    ArgumentStringTypeBehavior::SingleWord => 0,
436                    ArgumentStringTypeBehavior::QuotablePhrase => 1,
437                    ArgumentStringTypeBehavior::GreedyPhrase => 2,
438                };
439                VarInt(i).write(writer)
440            }
441            Self::Entity { flags } => flags.write(writer),
442            Self::ScoreHolder { flags } => flags.write(writer),
443            Self::Time { min } => min.write(writer),
444            Self::ResourceOrTag { identifier } => identifier.write_prefixed::<VarInt>(writer),
445            Self::ResourceOrTagKey { identifier } => identifier.write_prefixed::<VarInt>(writer),
446            Self::Resource { identifier } => identifier.write_prefixed::<VarInt>(writer),
447            Self::ResourceKey { identifier } => identifier.write_prefixed::<VarInt>(writer),
448            Self::ResourceSelector { identifier } => identifier.write_prefixed::<VarInt>(writer),
449            _ => Ok(()),
450        }
451    }
452}
453
454impl ArgumentType {
455    fn write_min_max<T: WriteTo>(
456        min: Option<T>,
457        max: Option<T>,
458        writer: &mut impl Write,
459    ) -> Result<()> {
460        // none = 0
461        // min = 1
462        // max = 2
463        // min & max = 3
464        (u8::from(min.is_some()) + u8::from(max.is_some()) + u8::from(max.is_some()))
465            .write(writer)?;
466
467        if let Some(min) = min {
468            min.write(writer)?;
469        }
470        if let Some(max) = max {
471            max.write(writer)?;
472        }
473
474        Ok(())
475    }
476}
477
478pub enum SuggestionType {
479    AskServer,
480    AllRecipes,
481    AvailableSounds,
482    SummonableEntities,
483}
484
485impl SuggestionType {
486    const fn as_str(&self) -> &str {
487        match self {
488            SuggestionType::AskServer => "minecraft:ask_server",
489            SuggestionType::AllRecipes => "minecraft:all_recipes",
490            SuggestionType::AvailableSounds => "minecraft:available_sounds",
491            SuggestionType::SummonableEntities => "minecraft:summonable_entities",
492        }
493    }
494}
495
496#[cfg(test)]
497mod tests {
498    use steel_utils::serial::WriteTo;
499
500    use super::{ArgumentType, CommandNode, CommandNodeInfo};
501
502    #[test]
503    fn restricted_nodes_set_the_26_2_protocol_flag() {
504        let node = CommandNode::new_literal(CommandNodeInfo::new(Vec::new()).restricted(), "op");
505        let mut encoded = Vec::new();
506
507        assert!(node.write(&mut encoded).is_ok());
508        assert_eq!(encoded.first().copied(), Some(1 | 32));
509    }
510
511    #[test]
512    fn command_node_flags_can_be_combined() {
513        let info = CommandNodeInfo::new(Vec::new())
514            .executable()
515            .restricted()
516            .redirect(0);
517        let node = CommandNode::new_literal(info, "alias");
518        let mut encoded = Vec::new();
519
520        assert!(node.write(&mut encoded).is_ok());
521        assert_eq!(encoded.first().copied(), Some(1 | 4 | 8 | 32));
522    }
523
524    #[test]
525    fn command_argument_tail_uses_the_26_3_registry_ids() {
526        for (argument, expected) in [
527            (ArgumentType::TemplateMirror, 49),
528            (ArgumentType::TemplateRotation, 50),
529            (ArgumentType::Heightmap, 51),
530            (ArgumentType::LootTable, 52),
531            (ArgumentType::LootPredicate, 53),
532            (ArgumentType::LootModifier, 54),
533            (ArgumentType::Dialog, 58),
534            (ArgumentType::SwingAnimation, 60),
535            (ArgumentType::Uuid, 61),
536        ] {
537            let mut encoded = Vec::new();
538            assert!(argument.write(&mut encoded).is_ok());
539            assert_eq!(encoded, [expected]);
540        }
541    }
542
543    #[test]
544    fn resource_selector_writes_its_registry_key() {
545        let mut encoded = Vec::new();
546        let argument = ArgumentType::ResourceSelector {
547            identifier: "minecraft:test_instance",
548        };
549
550        assert!(argument.write(&mut encoded).is_ok());
551        assert_eq!(&encoded[..2], [48, 23]);
552        assert_eq!(&encoded[2..], b"minecraft:test_instance");
553    }
554}