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