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