azalea_protocol/packets/game/
c_commands.rs

1use std::io::{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_usize.div_ceil(8) }>::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) -> Result<(), std::io::Error> {
65        let mut flags = FixedBitSet::<{ 2_usize.div_ceil(8) }>::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_usize.div_ceil(8) }>::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) -> Result<(), std::io::Error> {
170        let mut flags = FixedBitSet::<{ 2_usize.div_ceil(8) }>::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_usize.div_ceil(8) }>::azalea_read(buf)?;
186        if flags.index(5) || flags.index(6) || flags.index(7) {
187            warn!("The flags from a Brigadier node are over 31. This is a bug, BrigadierParser probably needs updating.",);
188        }
189
190        let node_type = u8::from(flags.index(0)) + (u8::from(flags.index(1)) * 2);
191        let is_executable = flags.index(2);
192        let has_redirect = flags.index(3);
193        let has_suggestions_type = flags.index(4);
194
195        let children = Vec::<u32>::azalea_read_var(buf)?;
196        let redirect_node = if has_redirect {
197            Some(u32::azalea_read_var(buf)?)
198        } else {
199            None
200        };
201
202        // argument node
203        if node_type == 2 {
204            let name = String::azalea_read(buf)?;
205            let parser = BrigadierParser::azalea_read(buf)?;
206            let suggestions_type = if has_suggestions_type {
207                Some(ResourceLocation::azalea_read(buf)?)
208            } else {
209                None
210            };
211            let node = BrigadierNodeStub {
212                is_executable,
213                children,
214                redirect_node,
215                node_type: NodeType::Argument {
216                    name,
217                    parser,
218                    suggestions_type,
219                },
220            };
221            return Ok(node);
222        }
223        // literal node
224        else if node_type == 1 {
225            let name = String::azalea_read(buf)?;
226            return Ok(BrigadierNodeStub {
227                is_executable,
228                children,
229                redirect_node,
230                node_type: NodeType::Literal { name },
231            });
232        }
233        Ok(BrigadierNodeStub {
234            is_executable,
235            children,
236            redirect_node,
237            node_type: NodeType::Root,
238        })
239    }
240}
241
242impl AzaleaWrite for BrigadierNodeStub {
243    fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
244        let mut flags = FixedBitSet::<{ 4_usize.div_ceil(8) }>::new();
245        if self.is_executable {
246            flags.set(2);
247        }
248        if self.redirect_node.is_some() {
249            flags.set(3);
250        }
251
252        match &self.node_type {
253            NodeType::Root => {
254                flags.azalea_write(buf)?;
255
256                self.children.azalea_write_var(buf)?;
257
258                if let Some(redirect) = self.redirect_node {
259                    redirect.azalea_write_var(buf)?;
260                }
261            }
262            NodeType::Literal { name } => {
263                flags.set(0);
264                flags.azalea_write(buf)?;
265
266                self.children.azalea_write_var(buf)?;
267
268                if let Some(redirect) = self.redirect_node {
269                    redirect.azalea_write_var(buf)?;
270                }
271
272                name.azalea_write(buf)?;
273            }
274            NodeType::Argument {
275                name,
276                parser,
277                suggestions_type,
278            } => {
279                flags.set(1);
280                if suggestions_type.is_some() {
281                    flags.set(4);
282                }
283                flags.azalea_write(buf)?;
284
285                self.children.azalea_write_var(buf)?;
286
287                if let Some(redirect) = self.redirect_node {
288                    redirect.azalea_write_var(buf)?;
289                }
290
291                name.azalea_write(buf)?;
292                parser.azalea_write(buf)?;
293
294                if let Some(suggestion) = suggestions_type {
295                    suggestion.azalea_write(buf)?;
296                }
297            }
298        }
299        Ok(())
300    }
301}
302
303#[derive(Debug, Clone, PartialEq)]
304pub enum NodeType {
305    Root,
306    Literal {
307        name: String,
308    },
309    Argument {
310        name: String,
311        parser: BrigadierParser,
312        suggestions_type: Option<ResourceLocation>,
313    },
314}
315
316impl BrigadierNodeStub {
317    #[must_use]
318    pub fn name(&self) -> Option<&str> {
319        match &self.node_type {
320            NodeType::Root => None,
321            NodeType::Literal { name } | NodeType::Argument { name, .. } => Some(name),
322        }
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    #[test]
331    fn test_brigadier_node_stub_root() {
332        let data = BrigadierNodeStub {
333            is_executable: false,
334            children: vec![1, 2],
335            redirect_node: None,
336            node_type: NodeType::Root,
337        };
338        let mut buf = Vec::new();
339        data.azalea_write(&mut buf).unwrap();
340        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
341        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
342        assert_eq!(data, read_data);
343    }
344
345    #[test]
346    fn test_brigadier_node_stub_literal() {
347        let data = BrigadierNodeStub {
348            is_executable: true,
349            children: vec![],
350            redirect_node: None,
351            node_type: NodeType::Literal {
352                name: "String".to_string(),
353            },
354        };
355        let mut buf = Vec::new();
356        data.azalea_write(&mut buf).unwrap();
357        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
358        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
359        assert_eq!(data, read_data);
360    }
361
362    #[test]
363    fn test_brigadier_node_stub_argument() {
364        let data = BrigadierNodeStub {
365            is_executable: false,
366            children: vec![6, 9],
367            redirect_node: Some(5),
368            node_type: NodeType::Argument {
369                name: "position".to_string(),
370                parser: BrigadierParser::Vec3,
371                suggestions_type: Some(ResourceLocation::new("minecraft:test_suggestion")),
372            },
373        };
374        let mut buf = Vec::new();
375        data.azalea_write(&mut buf).unwrap();
376        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
377        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
378        assert_eq!(data, read_data);
379    }
380}