azalea_protocol/packets/game/
c_commands.rs

1use std::io::{self, Cursor, Write};
2
3use azalea_buf::{AzBuf, AzaleaRead, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError};
4use azalea_core::{bitset::FixedBitSet, resource_location::ResourceLocation};
5use azalea_protocol_macros::ClientboundGamePacket;
6use tracing::warn;
7
8#[derive(Clone, Debug, AzBuf, ClientboundGamePacket)]
9pub struct ClientboundCommands {
10    pub entries: Vec<BrigadierNodeStub>,
11    #[var]
12    pub root_index: u32,
13}
14
15#[derive(Debug, Clone, PartialEq)]
16pub struct BrigadierNodeStub {
17    pub is_executable: bool,
18    pub children: Vec<u32>,
19    pub redirect_node: Option<u32>,
20    pub node_type: NodeType,
21}
22
23#[derive(Debug, Clone, Eq)]
24pub struct BrigadierNumber<T> {
25    pub min: Option<T>,
26    pub max: Option<T>,
27}
28impl<T> BrigadierNumber<T> {
29    pub fn new(min: Option<T>, max: Option<T>) -> BrigadierNumber<T> {
30        BrigadierNumber { min, max }
31    }
32}
33impl<T: PartialEq> PartialEq for BrigadierNumber<T> {
34    fn eq(&self, other: &Self) -> bool {
35        match (&self.min, &self.max, &other.min, &other.max) {
36            (Some(f_min), None, Some(s_min), None) => f_min == s_min,
37            (None, Some(f_max), None, Some(s_max)) => f_max == s_max,
38            (Some(f_min), Some(f_max), Some(s_min), Some(s_max)) => {
39                f_min == s_min && f_max == s_max
40            }
41            (None, None, None, None) => true,
42            _ => false,
43        }
44    }
45}
46
47impl<T: AzaleaRead> AzaleaRead for BrigadierNumber<T> {
48    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
49        let flags = FixedBitSet::<2>::azalea_read(buf)?;
50        let min = if flags.index(0) {
51            Some(T::azalea_read(buf)?)
52        } else {
53            None
54        };
55        let max = if flags.index(1) {
56            Some(T::azalea_read(buf)?)
57        } else {
58            None
59        };
60        Ok(BrigadierNumber { min, max })
61    }
62}
63impl<T: AzaleaWrite> AzaleaWrite for BrigadierNumber<T> {
64    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
65        let mut flags = FixedBitSet::<2>::new();
66        if self.min.is_some() {
67            flags.set(0);
68        }
69        if self.max.is_some() {
70            flags.set(1);
71        }
72        flags.azalea_write(buf)?;
73        if let Some(min) = &self.min {
74            min.azalea_write(buf)?;
75        }
76        if let Some(max) = &self.max {
77            max.azalea_write(buf)?;
78        }
79        Ok(())
80    }
81}
82
83#[derive(Debug, Clone, Copy, AzBuf, PartialEq, Eq)]
84pub enum BrigadierString {
85    /// Reads a single word
86    SingleWord = 0,
87    // If it starts with a ", keeps reading until another " (allowing escaping with \). Otherwise
88    // behaves the same as SINGLE_WORD
89    QuotablePhrase = 1,
90    // Reads the rest of the content after the cursor. Quotes will not be removed.
91    GreedyPhrase = 2,
92}
93
94// see ArgumentTypeInfo
95#[derive(Debug, Clone, AzBuf, PartialEq)]
96pub enum BrigadierParser {
97    Bool,
98    Float(BrigadierNumber<f32>),
99    Double(BrigadierNumber<f64>),
100    Integer(BrigadierNumber<i32>),
101    Long(BrigadierNumber<i64>),
102    String(BrigadierString),
103    Entity(EntityParser),
104    GameProfile,
105    BlockPos,
106    ColumnPos,
107    Vec3,
108    Vec2,
109    BlockState,
110    BlockPredicate,
111    ItemStack,
112    ItemPredicate,
113    Color,
114    FormattedText,
115    Style,
116    Message,
117    NbtCompoundTag,
118    NbtTag,
119    NbtPath,
120    Objective,
121    ObjectiveCriteria,
122    Operation,
123    Particle,
124    Angle,
125    Rotation,
126    ScoreboardSlot,
127    ScoreHolder { allows_multiple: bool },
128    Swizzle,
129    Team,
130    ItemSlot,
131    ItemSlots,
132    ResourceLocation,
133    Function,
134    EntityAnchor,
135    IntRange,
136    FloatRange,
137    Dimension,
138    GameMode,
139    Time { min: i32 },
140    ResourceOrTag { registry_key: ResourceLocation },
141    ResourceOrTagKey { registry_key: ResourceLocation },
142    Resource { registry_key: ResourceLocation },
143    ResourceKey { registry_key: ResourceLocation },
144    ResourceSelector { registry_key: ResourceLocation },
145    TemplateMirror,
146    TemplateRotation,
147    Heightmap,
148    LootTable,
149    LootPredicate,
150    LootModifier,
151    Uuid,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct EntityParser {
156    pub single: bool,
157    pub players_only: bool,
158}
159impl AzaleaRead for EntityParser {
160    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
161        let flags = FixedBitSet::<2>::azalea_read(buf)?;
162        Ok(EntityParser {
163            single: flags.index(0),
164            players_only: flags.index(1),
165        })
166    }
167}
168impl AzaleaWrite for EntityParser {
169    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
170        let mut flags = FixedBitSet::<2>::new();
171        if self.single {
172            flags.set(0);
173        }
174        if self.players_only {
175            flags.set(1);
176        }
177        flags.azalea_write(buf)?;
178        Ok(())
179    }
180}
181
182// TODO: BrigadierNodeStub should have more stuff
183impl AzaleaRead for BrigadierNodeStub {
184    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
185        let flags = FixedBitSet::<8>::azalea_read(buf)?;
186        if flags.index(5) || flags.index(6) || flags.index(7) {
187            warn!(
188                "The flags from a Brigadier node are over 31. This is a bug, BrigadierParser probably needs updating.",
189            );
190        }
191
192        let node_type = u8::from(flags.index(0)) + (u8::from(flags.index(1)) * 2);
193        let is_executable = flags.index(2);
194        let has_redirect = flags.index(3);
195        let has_suggestions_type = flags.index(4);
196
197        let children = Vec::<u32>::azalea_read_var(buf)?;
198        let redirect_node = if has_redirect {
199            Some(u32::azalea_read_var(buf)?)
200        } else {
201            None
202        };
203
204        // argument node
205        if node_type == 2 {
206            let name = String::azalea_read(buf)?;
207            let parser = BrigadierParser::azalea_read(buf)?;
208            let suggestions_type = if has_suggestions_type {
209                Some(ResourceLocation::azalea_read(buf)?)
210            } else {
211                None
212            };
213            let node = BrigadierNodeStub {
214                is_executable,
215                children,
216                redirect_node,
217                node_type: NodeType::Argument {
218                    name,
219                    parser,
220                    suggestions_type,
221                },
222            };
223            return Ok(node);
224        }
225        // literal node
226        else if node_type == 1 {
227            let name = String::azalea_read(buf)?;
228            return Ok(BrigadierNodeStub {
229                is_executable,
230                children,
231                redirect_node,
232                node_type: NodeType::Literal { name },
233            });
234        }
235        Ok(BrigadierNodeStub {
236            is_executable,
237            children,
238            redirect_node,
239            node_type: NodeType::Root,
240        })
241    }
242}
243
244impl AzaleaWrite for BrigadierNodeStub {
245    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
246        let mut flags = FixedBitSet::<4>::new();
247        if self.is_executable {
248            flags.set(2);
249        }
250        if self.redirect_node.is_some() {
251            flags.set(3);
252        }
253
254        match &self.node_type {
255            NodeType::Root => {
256                flags.azalea_write(buf)?;
257
258                self.children.azalea_write_var(buf)?;
259
260                if let Some(redirect) = self.redirect_node {
261                    redirect.azalea_write_var(buf)?;
262                }
263            }
264            NodeType::Literal { name } => {
265                flags.set(0);
266                flags.azalea_write(buf)?;
267
268                self.children.azalea_write_var(buf)?;
269
270                if let Some(redirect) = self.redirect_node {
271                    redirect.azalea_write_var(buf)?;
272                }
273
274                name.azalea_write(buf)?;
275            }
276            NodeType::Argument {
277                name,
278                parser,
279                suggestions_type,
280            } => {
281                flags.set(1);
282                if suggestions_type.is_some() {
283                    flags.set(4);
284                }
285                flags.azalea_write(buf)?;
286
287                self.children.azalea_write_var(buf)?;
288
289                if let Some(redirect) = self.redirect_node {
290                    redirect.azalea_write_var(buf)?;
291                }
292
293                name.azalea_write(buf)?;
294                parser.azalea_write(buf)?;
295
296                if let Some(suggestion) = suggestions_type {
297                    suggestion.azalea_write(buf)?;
298                }
299            }
300        }
301        Ok(())
302    }
303}
304
305#[derive(Debug, Clone, PartialEq)]
306pub enum NodeType {
307    Root,
308    Literal {
309        name: String,
310    },
311    Argument {
312        name: String,
313        parser: BrigadierParser,
314        suggestions_type: Option<ResourceLocation>,
315    },
316}
317
318impl BrigadierNodeStub {
319    #[must_use]
320    pub fn name(&self) -> Option<&str> {
321        match &self.node_type {
322            NodeType::Root => None,
323            NodeType::Literal { name } | NodeType::Argument { name, .. } => Some(name),
324        }
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331
332    #[test]
333    fn test_brigadier_node_stub_root() {
334        let data = BrigadierNodeStub {
335            is_executable: false,
336            children: vec![1, 2],
337            redirect_node: None,
338            node_type: NodeType::Root,
339        };
340        let mut buf = Vec::new();
341        data.azalea_write(&mut buf).unwrap();
342        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
343        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
344        assert_eq!(data, read_data);
345    }
346
347    #[test]
348    fn test_brigadier_node_stub_literal() {
349        let data = BrigadierNodeStub {
350            is_executable: true,
351            children: vec![],
352            redirect_node: None,
353            node_type: NodeType::Literal {
354                name: "String".to_string(),
355            },
356        };
357        let mut buf = Vec::new();
358        data.azalea_write(&mut buf).unwrap();
359        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
360        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
361        assert_eq!(data, read_data);
362    }
363
364    #[test]
365    fn test_brigadier_node_stub_argument() {
366        let data = BrigadierNodeStub {
367            is_executable: false,
368            children: vec![6, 9],
369            redirect_node: Some(5),
370            node_type: NodeType::Argument {
371                name: "position".to_string(),
372                parser: BrigadierParser::Vec3,
373                suggestions_type: Some(ResourceLocation::new("minecraft:test_suggestion")),
374            },
375        };
376        let mut buf = Vec::new();
377        data.azalea_write(&mut buf).unwrap();
378        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
379        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
380        assert_eq!(data, read_data);
381    }
382}