1use std::borrow::Cow;
2use std::io::Write;
3
4use anyhow::bail;
5use byteorder::WriteBytesExt;
6use valence_ident::Ident;
7
8use crate::{Decode, Encode, Packet, VarInt};
9
10#[derive(Clone, Debug, Encode, Decode, Packet)]
11pub struct CommandTreeS2c {
12 pub commands: Vec<Node>,
13 pub root_index: VarInt,
14}
15
16#[derive(Clone, Debug)]
17pub struct Node {
18 pub data: NodeData,
19 pub executable: bool,
20 pub children: Vec<VarInt>,
21 pub redirect_node: Option<VarInt>,
22}
23
24#[derive(Clone, Debug, PartialEq)]
25pub enum NodeData {
26 Root,
27 Literal {
28 name: String,
29 },
30 Argument {
31 name: String,
32 parser: Parser,
33 suggestion: Option<Suggestion>,
34 },
35}
36
37#[derive(Copy, Clone, PartialEq, Eq, Debug)]
38pub enum Suggestion {
39 AskServer,
40 AllRecipes,
41 AvailableSounds,
42 AvailableBiomes,
43 SummonableEntities,
44}
45
46#[derive(Clone, Debug, PartialEq)]
47pub enum Parser {
48 Bool,
49 Float { min: Option<f32>, max: Option<f32> },
50 Double { min: Option<f64>, max: Option<f64> },
51 Integer { min: Option<i32>, max: Option<i32> },
52 Long { min: Option<i64>, max: Option<i64> },
53 String(StringArg),
54 Entity { single: bool, only_players: bool },
55 GameProfile,
56 BlockPos,
57 ColumnPos,
58 Vec3,
59 Vec2,
60 BlockState,
61 BlockPredicate,
62 ItemStack,
63 ItemPredicate,
64 Color,
65 Component,
66 Message,
67 NbtCompoundTag,
68 NbtTag,
69 NbtPath,
70 Objective,
71 ObjectiveCriteria,
72 Operation,
73 Particle,
74 Angle,
75 Rotation,
76 ScoreboardSlot,
77 ScoreHolder { allow_multiple: bool },
78 Swizzle,
79 Team,
80 ItemSlot,
81 ResourceLocation,
82 Function,
83 EntityAnchor,
84 IntRange,
85 FloatRange,
86 Dimension,
87 GameMode,
88 Time,
89 ResourceOrTag { registry: Ident<String> },
90 ResourceOrTagKey { registry: Ident<String> },
91 Resource { registry: Ident<String> },
92 ResourceKey { registry: Ident<String> },
93 TemplateMirror,
94 TemplateRotation,
95 Uuid,
96}
97
98#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
99pub enum StringArg {
100 SingleWord,
101 QuotablePhrase,
102 GreedyPhrase,
103}
104
105impl Encode for Node {
106 fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
107 let node_type = match &self.data {
108 NodeData::Root => 0,
109 NodeData::Literal { .. } => 1,
110 NodeData::Argument { .. } => 2,
111 };
112
113 let has_suggestion = matches!(
114 &self.data,
115 NodeData::Argument {
116 suggestion: Some(_),
117 ..
118 }
119 );
120
121 let flags: u8 = node_type
122 | (u8::from(self.executable) * 0x04)
123 | (u8::from(self.redirect_node.is_some()) * 0x08)
124 | (u8::from(has_suggestion) * 0x10);
125
126 w.write_u8(flags)?;
127
128 self.children.encode(&mut w)?;
129
130 if let Some(redirect_node) = self.redirect_node {
131 redirect_node.encode(&mut w)?;
132 }
133
134 match &self.data {
135 NodeData::Root => {}
136 NodeData::Literal { name } => {
137 name.encode(&mut w)?;
138 }
139 NodeData::Argument {
140 name,
141 parser,
142 suggestion,
143 } => {
144 name.encode(&mut w)?;
145 parser.encode(&mut w)?;
146
147 if let Some(suggestion) = suggestion {
148 match suggestion {
149 Suggestion::AskServer => "ask_server",
150 Suggestion::AllRecipes => "all_recipes",
151 Suggestion::AvailableSounds => "available_sounds",
152 Suggestion::AvailableBiomes => "available_biomes",
153 Suggestion::SummonableEntities => "summonable_entities",
154 }
155 .encode(&mut w)?;
156 }
157 }
158 }
159
160 Ok(())
161 }
162}
163
164impl<'a> Decode<'a> for Node {
165 fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
166 let flags = u8::decode(r)?;
167
168 let children = Vec::decode(r)?;
169
170 let redirect_node = if flags & 0x08 != 0 {
171 Some(VarInt::decode(r)?)
172 } else {
173 None
174 };
175
176 let node_data = match flags & 0x3 {
177 0 => NodeData::Root,
178 1 => NodeData::Literal {
179 name: <String>::decode(r)?,
180 },
181 2 => NodeData::Argument {
182 name: <String>::decode(r)?,
183 parser: Parser::decode(r)?,
184 suggestion: if flags & 0x10 != 0 {
185 Some(match Ident::<Cow<str>>::decode(r)?.as_str() {
186 "minecraft:ask_server" => Suggestion::AskServer,
187 "minecraft:all_recipes" => Suggestion::AllRecipes,
188 "minecraft:available_sounds" => Suggestion::AvailableSounds,
189 "minecraft:available_biomes" => Suggestion::AvailableBiomes,
190 "minecraft:summonable_entities" => Suggestion::SummonableEntities,
191 other => bail!("unknown command suggestion type of \"{other}\""),
192 })
193 } else {
194 None
195 },
196 },
197 n => bail!("invalid node type of {n}"),
198 };
199
200 Ok(Self {
201 children,
202 data: node_data,
203 executable: flags & 0x04 != 0,
204 redirect_node,
205 })
206 }
207}
208
209impl Encode for Parser {
210 fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
211 match self {
212 Parser::Bool => 0_u8.encode(&mut w)?,
213 Parser::Float { min, max } => {
214 1_u8.encode(&mut w)?;
215
216 (u8::from(min.is_some()) | (u8::from(max.is_some()) * 0x2)).encode(&mut w)?;
217
218 if let Some(min) = min {
219 min.encode(&mut w)?;
220 }
221
222 if let Some(max) = max {
223 max.encode(&mut w)?;
224 }
225 }
226 Parser::Double { min, max } => {
227 2_u8.encode(&mut w)?;
228
229 (u8::from(min.is_some()) | (u8::from(max.is_some()) * 0x2)).encode(&mut w)?;
230
231 if let Some(min) = min {
232 min.encode(&mut w)?;
233 }
234
235 if let Some(max) = max {
236 max.encode(&mut w)?;
237 }
238 }
239 Parser::Integer { min, max } => {
240 3_u8.encode(&mut w)?;
241
242 (u8::from(min.is_some()) | (u8::from(max.is_some()) * 0x2)).encode(&mut w)?;
243
244 if let Some(min) = min {
245 min.encode(&mut w)?;
246 }
247
248 if let Some(max) = max {
249 max.encode(&mut w)?;
250 }
251 }
252 Parser::Long { min, max } => {
253 4_u8.encode(&mut w)?;
254
255 (u8::from(min.is_some()) | (u8::from(max.is_some()) * 0x2)).encode(&mut w)?;
256
257 if let Some(min) = min {
258 min.encode(&mut w)?;
259 }
260
261 if let Some(max) = max {
262 max.encode(&mut w)?;
263 }
264 }
265 Parser::String(arg) => {
266 5_u8.encode(&mut w)?;
267 arg.encode(&mut w)?;
268 }
269 Parser::Entity {
270 single,
271 only_players,
272 } => {
273 6_u8.encode(&mut w)?;
274 (u8::from(*single) | (u8::from(*only_players) * 0x2)).encode(&mut w)?;
275 }
276 Parser::GameProfile => 7_u8.encode(&mut w)?,
277 Parser::BlockPos => 8_u8.encode(&mut w)?,
278 Parser::ColumnPos => 9_u8.encode(&mut w)?,
279 Parser::Vec3 => 10_u8.encode(&mut w)?,
280 Parser::Vec2 => 11_u8.encode(&mut w)?,
281 Parser::BlockState => 12_u8.encode(&mut w)?,
282 Parser::BlockPredicate => 13_u8.encode(&mut w)?,
283 Parser::ItemStack => 14_u8.encode(&mut w)?,
284 Parser::ItemPredicate => 15_u8.encode(&mut w)?,
285 Parser::Color => 16_u8.encode(&mut w)?,
286 Parser::Component => 17_u8.encode(&mut w)?,
287 Parser::Message => 18_u8.encode(&mut w)?,
288 Parser::NbtCompoundTag => 19_u8.encode(&mut w)?,
289 Parser::NbtTag => 20_u8.encode(&mut w)?,
290 Parser::NbtPath => 21_u8.encode(&mut w)?,
291 Parser::Objective => 22_u8.encode(&mut w)?,
292 Parser::ObjectiveCriteria => 23_u8.encode(&mut w)?,
293 Parser::Operation => 24_u8.encode(&mut w)?,
294 Parser::Particle => 25_u8.encode(&mut w)?,
295 Parser::Angle => 26_u8.encode(&mut w)?,
296 Parser::Rotation => 27_u8.encode(&mut w)?,
297 Parser::ScoreboardSlot => 28_u8.encode(&mut w)?,
298 Parser::ScoreHolder { allow_multiple } => {
299 29_u8.encode(&mut w)?;
300 allow_multiple.encode(&mut w)?;
301 }
302 Parser::Swizzle => 30_u8.encode(&mut w)?,
303 Parser::Team => 31_u8.encode(&mut w)?,
304 Parser::ItemSlot => 32_u8.encode(&mut w)?,
305 Parser::ResourceLocation => 33_u8.encode(&mut w)?,
306 Parser::Function => 34_u8.encode(&mut w)?,
307 Parser::EntityAnchor => 35_u8.encode(&mut w)?,
308 Parser::IntRange => 36_u8.encode(&mut w)?,
309 Parser::FloatRange => 37_u8.encode(&mut w)?,
310 Parser::Dimension => 38_u8.encode(&mut w)?,
311 Parser::GameMode => 39_u8.encode(&mut w)?,
312 Parser::Time => 40_u8.encode(&mut w)?,
313 Parser::ResourceOrTag { registry } => {
314 41_u8.encode(&mut w)?;
315 registry.encode(&mut w)?;
316 }
317 Parser::ResourceOrTagKey { registry } => {
318 42_u8.encode(&mut w)?;
319 registry.encode(&mut w)?;
320 }
321 Parser::Resource { registry } => {
322 43_u8.encode(&mut w)?;
323 registry.encode(&mut w)?;
324 }
325 Parser::ResourceKey { registry } => {
326 44_u8.encode(&mut w)?;
327 registry.encode(&mut w)?;
328 }
329 Parser::TemplateMirror => 45_u8.encode(&mut w)?,
330 Parser::TemplateRotation => 46_u8.encode(&mut w)?,
331 Parser::Uuid => 47_u8.encode(&mut w)?,
332 }
333
334 Ok(())
335 }
336}
337
338impl<'a> Decode<'a> for Parser {
339 fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self> {
340 fn decode_min_max<'a, T: Decode<'a>>(
341 r: &mut &'a [u8],
342 ) -> anyhow::Result<(Option<T>, Option<T>)> {
343 let flags = u8::decode(r)?;
344
345 let min = if flags & 0x1 != 0 {
346 Some(T::decode(r)?)
347 } else {
348 None
349 };
350
351 let max = if flags & 0x2 != 0 {
352 Some(T::decode(r)?)
353 } else {
354 None
355 };
356
357 Ok((min, max))
358 }
359
360 Ok(match u8::decode(r)? {
361 0 => Self::Bool,
362 1 => {
363 let (min, max) = decode_min_max(r)?;
364 Self::Float { min, max }
365 }
366 2 => {
367 let (min, max) = decode_min_max(r)?;
368 Self::Double { min, max }
369 }
370 3 => {
371 let (min, max) = decode_min_max(r)?;
372 Self::Integer { min, max }
373 }
374 4 => {
375 let (min, max) = decode_min_max(r)?;
376 Self::Long { min, max }
377 }
378 5 => Self::String(StringArg::decode(r)?),
379 6 => {
380 let flags = u8::decode(r)?;
381 Self::Entity {
382 single: flags & 0x1 != 0,
383 only_players: flags & 0x2 != 0,
384 }
385 }
386 7 => Self::GameProfile,
387 8 => Self::BlockPos,
388 9 => Self::ColumnPos,
389 10 => Self::Vec3,
390 11 => Self::Vec2,
391 12 => Self::BlockState,
392 13 => Self::BlockPredicate,
393 14 => Self::ItemStack,
394 15 => Self::ItemPredicate,
395 16 => Self::Color,
396 17 => Self::Component,
397 18 => Self::Message,
398 19 => Self::NbtCompoundTag,
399 20 => Self::NbtTag,
400 21 => Self::NbtPath,
401 22 => Self::Objective,
402 23 => Self::ObjectiveCriteria,
403 24 => Self::Operation,
404 25 => Self::Particle,
405 26 => Self::Angle,
406 27 => Self::Rotation,
407 28 => Self::ScoreboardSlot,
408 29 => Self::ScoreHolder {
409 allow_multiple: bool::decode(r)?,
410 },
411 30 => Self::Swizzle,
412 31 => Self::Team,
413 32 => Self::ItemSlot,
414 33 => Self::ResourceLocation,
415 34 => Self::Function,
416 35 => Self::EntityAnchor,
417 36 => Self::IntRange,
418 37 => Self::FloatRange,
419 38 => Self::Dimension,
420 39 => Self::GameMode,
421 40 => Self::Time,
422 41 => Self::ResourceOrTag {
423 registry: Ident::decode(r)?,
424 },
425 42 => Self::ResourceOrTagKey {
426 registry: Ident::decode(r)?,
427 },
428 43 => Self::Resource {
429 registry: Ident::decode(r)?,
430 },
431 44 => Self::ResourceKey {
432 registry: Ident::decode(r)?,
433 },
434 45 => Self::TemplateMirror,
435 46 => Self::TemplateRotation,
436 47 => Self::Uuid,
437 n => bail!("unknown command parser ID of {n}"),
438 })
439 }
440}