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 SingleWord = 0,
87 QuotablePhrase = 1,
90 GreedyPhrase = 2,
92}
93
94#[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
182impl 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 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 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}