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