azalea_protocol/packets/game/
c_commands.rs

1use std::io::{self, Cursor, Write};
2
3use azalea_buf::{AzBuf, AzBufVar, BufReadError};
4use azalea_core::bitset::FixedBitSet;
5use azalea_protocol_macros::ClientboundGamePacket;
6use azalea_registry::identifier::Identifier;
7use tracing::warn;
8
9#[derive(AzBuf, ClientboundGamePacket, Clone, Debug, PartialEq)]
10pub struct ClientboundCommands {
11    pub entries: Vec<BrigadierNodeStub>,
12    #[var]
13    pub root_index: u32,
14}
15
16#[derive(Clone, Debug, 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(Clone, Debug, 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: AzBuf> AzBuf 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    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(AzBuf, Clone, Copy, Debug, Eq, PartialEq)]
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 ArgumentTypeInfos.java
95#[derive(AzBuf, Clone, Debug, 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    HexColor,
115    FormattedText,
116    Style,
117    Message,
118    NbtCompoundTag,
119    NbtTag,
120    NbtPath,
121    Objective,
122    ObjectiveCriteria,
123    Operation,
124    Particle,
125    Angle,
126    Rotation,
127    ScoreboardSlot,
128    ScoreHolder { allows_multiple: bool },
129    Swizzle,
130    Team,
131    ItemSlot,
132    ItemSlots,
133    Identifier,
134    Function,
135    EntityAnchor,
136    IntRange,
137    FloatRange,
138    Dimension,
139    GameMode,
140    Time { min: i32 },
141    ResourceOrTag { registry_key: Identifier },
142    ResourceOrTagKey { registry_key: Identifier },
143    Resource { registry_key: Identifier },
144    ResourceKey { registry_key: Identifier },
145    ResourceSelector { registry_key: Identifier },
146    TemplateMirror,
147    TemplateRotation,
148    Heightmap,
149    LootTable,
150    LootPredicate,
151    LootModifier,
152    Dialog,
153    Uuid,
154}
155
156#[derive(Clone, Debug, Eq, PartialEq)]
157pub struct EntityParser {
158    pub single: bool,
159    pub players_only: bool,
160}
161impl AzBuf for EntityParser {
162    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
163        let flags = FixedBitSet::<2>::azalea_read(buf)?;
164        Ok(EntityParser {
165            single: flags.index(0),
166            players_only: flags.index(1),
167        })
168    }
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 AzBuf 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(6) || flags.index(7) {
187            warn!(
188                "The flags from a Brigadier node are over 63. 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        let is_restricted = flags.index(5);
197
198        let children = Vec::<u32>::azalea_read_var(buf)?;
199        let redirect_node = if has_redirect {
200            Some(u32::azalea_read_var(buf)?)
201        } else {
202            None
203        };
204
205        // argument node
206        if node_type == 2 {
207            let name = String::azalea_read(buf)?;
208            let parser = BrigadierParser::azalea_read(buf)?;
209            let suggestions_type = if has_suggestions_type {
210                Some(Identifier::azalea_read(buf)?)
211            } else {
212                None
213            };
214            Ok(BrigadierNodeStub {
215                is_executable,
216                children,
217                redirect_node,
218                node_type: NodeType::Argument {
219                    name,
220                    parser,
221                    suggestions_type,
222                },
223                is_restricted,
224            })
225        }
226        // literal node
227        else if node_type == 1 {
228            let name = String::azalea_read(buf)?;
229            Ok(BrigadierNodeStub {
230                is_executable,
231                children,
232                redirect_node,
233                node_type: NodeType::Literal { name },
234                is_restricted,
235            })
236        } else {
237            Ok(BrigadierNodeStub {
238                is_executable,
239                children,
240                redirect_node,
241                node_type: NodeType::Root,
242                is_restricted,
243            })
244        }
245    }
246    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
247        let mut flags = FixedBitSet::<4>::new();
248        if self.is_executable {
249            flags.set(2);
250        }
251        if self.redirect_node.is_some() {
252            flags.set(3);
253        }
254
255        match &self.node_type {
256            NodeType::Root => {
257                flags.azalea_write(buf)?;
258
259                self.children.azalea_write_var(buf)?;
260
261                if let Some(redirect) = self.redirect_node {
262                    redirect.azalea_write_var(buf)?;
263                }
264            }
265            NodeType::Literal { name } => {
266                flags.set(0);
267                flags.azalea_write(buf)?;
268
269                self.children.azalea_write_var(buf)?;
270
271                if let Some(redirect) = self.redirect_node {
272                    redirect.azalea_write_var(buf)?;
273                }
274
275                name.azalea_write(buf)?;
276            }
277            NodeType::Argument {
278                name,
279                parser,
280                suggestions_type,
281            } => {
282                flags.set(1);
283                if suggestions_type.is_some() {
284                    flags.set(4);
285                }
286                flags.azalea_write(buf)?;
287
288                self.children.azalea_write_var(buf)?;
289
290                if let Some(redirect) = self.redirect_node {
291                    redirect.azalea_write_var(buf)?;
292                }
293
294                name.azalea_write(buf)?;
295                parser.azalea_write(buf)?;
296
297                if let Some(suggestion) = suggestions_type {
298                    suggestion.azalea_write(buf)?;
299                }
300            }
301        }
302        Ok(())
303    }
304}
305
306#[derive(Clone, Debug, PartialEq)]
307pub enum NodeType {
308    Root,
309    Literal {
310        name: String,
311    },
312    Argument {
313        name: String,
314        parser: BrigadierParser,
315        suggestions_type: Option<Identifier>,
316    },
317}
318
319impl BrigadierNodeStub {
320    #[must_use]
321    pub fn name(&self) -> Option<&str> {
322        match &self.node_type {
323            NodeType::Root => None,
324            NodeType::Literal { name } | NodeType::Argument { name, .. } => Some(name),
325        }
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    #[test]
334    fn test_brigadier_node_stub_root() {
335        let data = BrigadierNodeStub {
336            is_executable: false,
337            children: vec![1, 2],
338            redirect_node: None,
339            node_type: NodeType::Root,
340            is_restricted: false,
341        };
342        let mut buf = Vec::new();
343        data.azalea_write(&mut buf).unwrap();
344        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
345        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
346        assert_eq!(data, read_data);
347    }
348
349    #[test]
350    fn test_brigadier_node_stub_literal() {
351        let data = BrigadierNodeStub {
352            is_executable: true,
353            children: vec![],
354            redirect_node: None,
355            node_type: NodeType::Literal {
356                name: "String".to_owned(),
357            },
358            is_restricted: false,
359        };
360        let mut buf = Vec::new();
361        data.azalea_write(&mut buf).unwrap();
362        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
363        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
364        assert_eq!(data, read_data);
365    }
366
367    #[test]
368    fn test_brigadier_node_stub_argument() {
369        let data = BrigadierNodeStub {
370            is_executable: false,
371            children: vec![6, 9],
372            redirect_node: Some(5),
373            node_type: NodeType::Argument {
374                name: "position".to_owned(),
375                parser: BrigadierParser::Vec3,
376                suggestions_type: Some(Identifier::new("minecraft:test_suggestion")),
377            },
378            is_restricted: false,
379        };
380        let mut buf = Vec::new();
381        data.azalea_write(&mut buf).unwrap();
382        let mut data_cursor: Cursor<&[u8]> = Cursor::new(&buf);
383        let read_data = BrigadierNodeStub::azalea_read(&mut data_cursor).unwrap();
384        assert_eq!(data, read_data);
385    }
386}