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#[derive(Debug, Clone, AzBuf, PartialEq)]
95pub enum BrigadierParser {
96    Bool,
97    Float(BrigadierNumber<f32>),
98    Double(BrigadierNumber<f64>),
99    Integer(BrigadierNumber<i32>),
100    Long(BrigadierNumber<i64>),
101    String(BrigadierString),
102    Entity(EntityParser),
103    GameProfile,
104    BlockPos,
105    ColumnPos,
106    Vec3,
107    Vec2,
108    BlockState,
109    BlockPredicate,
110    ItemStack,
111    ItemPredicate,
112    Color,
113    FormattedText,
114    Style,
115    Message,
116    NbtCompoundTag,
117    NbtTag,
118    NbtPath,
119    Objective,
120    ObjectiveCriteria,
121    Operation,
122    Particle,
123    Angle,
124    Rotation,
125    ScoreboardSlot,
126    ScoreHolder { allows_multiple: bool },
127    Swizzle,
128    Team,
129    ItemSlot,
130    ItemSlots,
131    ResourceLocation,
132    Function,
133    EntityAnchor,
134    IntRange,
135    FloatRange,
136    Dimension,
137    GameMode,
138    Time { min: i32 },
139    ResourceOrTag { registry_key: ResourceLocation },
140    ResourceOrTagKey { registry_key: ResourceLocation },
141    Resource { registry_key: ResourceLocation },
142    ResourceKey { registry_key: ResourceLocation },
143    TemplateMirror,
144    TemplateRotation,
145    Heightmap,
146    LootTable,
147    LootPredicate,
148    LootModifier,
149    Uuid,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct EntityParser {
154    pub single: bool,
155    pub players_only: bool,
156}
157impl AzaleaRead for EntityParser {
158    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
159        let flags = FixedBitSet::<{ 2_usize.div_ceil(8) }>::azalea_read(buf)?;
160        Ok(EntityParser {
161            single: flags.index(0),
162            players_only: flags.index(1),
163        })
164    }
165}
166impl AzaleaWrite for EntityParser {
167    fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
168        let mut flags = FixedBitSet::<{ 2_usize.div_ceil(8) }>::new();
169        if self.single {
170            flags.set(0);
171        }
172        if self.players_only {
173            flags.set(1);
174        }
175        flags.azalea_write(buf)?;
176        Ok(())
177    }
178}
179
180// TODO: BrigadierNodeStub should have more stuff
181impl AzaleaRead for BrigadierNodeStub {
182    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
183        let flags = FixedBitSet::<{ 8_usize.div_ceil(8) }>::azalea_read(buf)?;
184        if flags.index(5) || flags.index(6) || flags.index(7) {
185            warn!("Warning: The flags from a Brigadier node are over 31. This is probably a bug.",);
186        }
187
188        let node_type = u8::from(flags.index(0)) + (u8::from(flags.index(1)) * 2);
189        let is_executable = flags.index(2);
190        let has_redirect = flags.index(3);
191        let has_suggestions_type = flags.index(4);
192
193        let children = Vec::<u32>::azalea_read_var(buf)?;
194        let redirect_node = if has_redirect {
195            Some(u32::azalea_read_var(buf)?)
196        } else {
197            None
198        };
199
200        // argument node
201        if node_type == 2 {
202            let name = String::azalea_read(buf)?;
203            let parser = BrigadierParser::azalea_read(buf)?;
204            let suggestions_type = if has_suggestions_type {
205                Some(ResourceLocation::azalea_read(buf)?)
206            } else {
207                None
208            };
209            let node = BrigadierNodeStub {
210                is_executable,
211                children,
212                redirect_node,
213                node_type: NodeType::Argument {
214                    name,
215                    parser,
216                    suggestions_type,
217                },
218            };
219            return Ok(node);
220        }
221        // literal node
222        else if node_type == 1 {
223            let name = String::azalea_read(buf)?;
224            return Ok(BrigadierNodeStub {
225                is_executable,
226                children,
227                redirect_node,
228                node_type: NodeType::Literal { name },
229            });
230        }
231        Ok(BrigadierNodeStub {
232            is_executable,
233            children,
234            redirect_node,
235            node_type: NodeType::Root,
236        })
237    }
238}
239
240impl AzaleaWrite for BrigadierNodeStub {
241    fn azalea_write(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
242        let mut flags = FixedBitSet::<{ 4_usize.div_ceil(8) }>::new();
243        if self.is_executable {
244            flags.set(2);
245        }
246        if self.redirect_node.is_some() {
247            flags.set(3);
248        }
249
250        match &self.node_type {
251            NodeType::Root => {
252                flags.azalea_write(buf)?;
253
254                self.children.azalea_write_var(buf)?;
255
256                if let Some(redirect) = self.redirect_node {
257                    redirect.azalea_write_var(buf)?;
258                }
259            }
260            NodeType::Literal { name } => {
261                flags.set(0);
262                flags.azalea_write(buf)?;
263
264                self.children.azalea_write_var(buf)?;
265
266                if let Some(redirect) = self.redirect_node {
267                    redirect.azalea_write_var(buf)?;
268                }
269
270                name.azalea_write(buf)?;
271            }
272            NodeType::Argument {
273                name,
274                parser,
275                suggestions_type,
276            } => {
277                flags.set(1);
278                if suggestions_type.is_some() {
279                    flags.set(4);
280                }
281                flags.azalea_write(buf)?;
282
283                self.children.azalea_write_var(buf)?;
284
285                if let Some(redirect) = self.redirect_node {
286                    redirect.azalea_write_var(buf)?;
287                }
288
289                name.azalea_write(buf)?;
290                parser.azalea_write(buf)?;
291
292                if let Some(suggestion) = suggestions_type {
293                    suggestion.azalea_write(buf)?;
294                }
295            }
296        }
297        Ok(())
298    }
299}
300
301#[derive(Debug, Clone, PartialEq)]
302pub enum NodeType {
303    Root,
304    Literal {
305        name: String,
306    },
307    Argument {
308        name: String,
309        parser: BrigadierParser,
310        suggestions_type: Option<ResourceLocation>,
311    },
312}
313
314impl BrigadierNodeStub {
315    #[must_use]
316    pub fn name(&self) -> Option<&str> {
317        match &self.node_type {
318            NodeType::Root => None,
319            NodeType::Literal { name } | NodeType::Argument { name, .. } => Some(name),
320        }
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn test_brigadier_node_stub_root() {
330        let data = BrigadierNodeStub {
331            is_executable: false,
332            children: vec![1, 2],
333            redirect_node: None,
334            node_type: NodeType::Root,
335        };
336        let mut buf = Vec::new();
337        data.azalea_write(&mut buf).unwrap();
338        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
339        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
340        assert_eq!(data, read_data);
341    }
342
343    #[test]
344    fn test_brigadier_node_stub_literal() {
345        let data = BrigadierNodeStub {
346            is_executable: true,
347            children: vec![],
348            redirect_node: None,
349            node_type: NodeType::Literal {
350                name: "String".to_string(),
351            },
352        };
353        let mut buf = Vec::new();
354        data.azalea_write(&mut buf).unwrap();
355        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
356        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
357        assert_eq!(data, read_data);
358    }
359
360    #[test]
361    fn test_brigadier_node_stub_argument() {
362        let data = BrigadierNodeStub {
363            is_executable: false,
364            children: vec![6, 9],
365            redirect_node: Some(5),
366            node_type: NodeType::Argument {
367                name: "position".to_string(),
368                parser: BrigadierParser::Vec3,
369                suggestions_type: Some(ResourceLocation::new("minecraft:test_suggestion")),
370            },
371        };
372        let mut buf = Vec::new();
373        data.azalea_write(&mut buf).unwrap();
374        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
375        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
376        assert_eq!(data, read_data);
377    }
378}