1#![expect(
15 clippy::pub_underscore_fields,
16 clippy::used_underscore_binding,
17 reason = "required for de-/serialization macros"
18)]
19#![expect(
20 clippy::min_ident_chars,
21 reason = "those identifiers are well-known and clear from the context"
22)]
23
24mod active_object;
25mod arrays;
26mod binary;
27mod compressed;
28mod node_box;
29mod options;
30mod primitives;
31mod strings;
32mod tile;
33mod vectors;
34
35use crate::itos;
36use crate::wire::deser::Deserialize;
37use crate::wire::deser::DeserializeError;
38use crate::wire::deser::DeserializeResult;
39use crate::wire::deser::Deserializer;
40use crate::wire::packet::LATEST_PROTOCOL_VERSION;
41use crate::wire::packet::SER_FMT_HIGHEST_READ;
42use crate::wire::ser::Serialize;
43use crate::wire::ser::SerializeResult;
44use crate::wire::ser::Serializer;
45use crate::wire::ser::VecSerializer;
46use crate::wire::util::compress_zlib;
47use crate::wire::util::decompress_zlib;
48use crate::wire::util::deserialize_json_string_if_needed;
49use crate::wire::util::next_word;
50use crate::wire::util::serialize_json_string_if_needed;
51use crate::wire::util::skip_whitespace;
52use crate::wire::util::split_by_whitespace;
53use crate::wire::util::stoi;
54use crate::wire::util::zstd_compress;
55use crate::wire::util::zstd_decompress;
56pub use active_object::*;
57use anyhow::anyhow;
58use anyhow::bail;
59pub use arrays::*;
60pub use binary::*;
61pub use compressed::*;
62use luanti_protocol_derive::LuantiDeserialize;
63use luanti_protocol_derive::LuantiSerialize;
64pub use node_box::*;
65pub use options::*;
66pub use primitives::*;
67use std::marker::PhantomData;
68pub use strings::*;
69pub use tile::*;
70pub use vectors::*;
71
72pub type CommandId = u8;
73
74#[derive(Debug, Clone, Copy, PartialEq)]
75pub enum CommandDirection {
76 ToClient,
77 ToServer,
78}
79
80impl CommandDirection {
81 #[must_use]
82 pub fn for_send(remote_is_server: bool) -> Self {
83 if remote_is_server {
84 CommandDirection::ToServer
85 } else {
86 CommandDirection::ToClient
87 }
88 }
89
90 #[must_use]
91 pub fn for_receive(remote_is_server: bool) -> Self {
92 Self::for_send(remote_is_server).flip()
93 }
94
95 #[must_use]
96 pub fn flip(&self) -> Self {
97 match self {
98 CommandDirection::ToClient => CommandDirection::ToServer,
99 CommandDirection::ToServer => CommandDirection::ToClient,
100 }
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq)]
105pub struct ProtocolContext {
106 pub dir: CommandDirection,
107 pub protocol_version: u16,
108 pub ser_fmt: u8,
109}
110
111impl ProtocolContext {
112 #[must_use]
113 pub fn latest_for_receive(remote_is_server: bool) -> Self {
114 Self {
115 dir: CommandDirection::for_receive(remote_is_server),
116 protocol_version: LATEST_PROTOCOL_VERSION,
117 ser_fmt: SER_FMT_HIGHEST_READ,
118 }
119 }
120
121 #[must_use]
122 pub fn latest_for_send(remote_is_server: bool) -> Self {
123 Self {
124 dir: CommandDirection::for_send(remote_is_server),
125 protocol_version: LATEST_PROTOCOL_VERSION,
126 ser_fmt: SER_FMT_HIGHEST_READ,
127 }
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
132pub struct AddedObject {
133 pub id: u16,
134 pub typ: u8,
135 #[wrap(Wrapped32<GenericInitData>)]
136 pub init_data: GenericInitData,
137}
138
139#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
140pub struct MediaFileData {
141 pub name: String,
142 #[wrap(BinaryData32)]
143 pub data: Vec<u8>,
144}
145
146#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
147pub struct MediaAnnouncement {
148 pub name: String,
149 pub sha1_base64: String,
150}
151
152#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
153pub struct SkyColor {
154 pub day_sky: SColor,
155 pub day_horizon: SColor,
156 pub dawn_sky: SColor,
157 pub dawn_horizon: SColor,
158 pub night_sky: SColor,
159 pub night_horizon: SColor,
160 pub indoors: SColor,
161}
162
163#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
164pub struct SunParams {
165 pub visible: bool,
166 pub texture: String,
167 pub tonemap: String,
168 pub sunrise: String,
169 pub sunrise_visible: bool,
170 pub scale: f32,
171}
172
173#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
174pub struct MoonParams {
175 pub visible: bool,
176 pub texture: String,
177 pub tonemap: String,
178 pub scale: f32,
179}
180#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
181pub struct StarParams {
182 pub visible: bool,
183 pub count: u32,
184 pub starcolor: SColor,
185 pub scale: f32,
186 pub day_opacity: Option<f32>,
187}
188
189#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
190pub struct MinimapMode {
191 pub typ: u16,
192 pub label: String,
193 pub size: u16,
194 pub texture: String,
195 pub scale: u16,
196}
197
198#[derive(Debug, Clone, PartialEq)]
199pub struct PlayerPos {
200 pub position: v3f, pub speed: v3f, pub pitch: f32, pub yaw: f32, pub keys_pressed: u32, pub fov: f32, pub wanted_range: u8,
207
208 pub camera_inverted: bool,
209 pub movement_speed: f32,
210 pub movement_direction: f32,
211}
212
213impl Serialize for PlayerPos {
214 type Input = Self;
215 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
216 let s_position = (value.position * 100_f32).as_v3s32();
217 let s_speed = (value.speed * 100_f32).as_v3s32();
218 let s_pitch = (value.pitch * 100_f32).round() as s32;
219 let s_yaw = (value.yaw * 100_f32).round() as s32;
220 let s_fov = (value.fov * 80_f32).round() as u8;
222 let bits = u8::from(value.camera_inverted);
223
224 v3s32::serialize(&s_position, ser)?;
225 v3s32::serialize(&s_speed, ser)?;
226 i32::serialize(&s_pitch, ser)?;
227 i32::serialize(&s_yaw, ser)?;
228 u32::serialize(&value.keys_pressed, ser)?;
229 u8::serialize(&s_fov, ser)?;
230 u8::serialize(&value.wanted_range, ser)?;
231 u8::serialize(&bits, ser)?;
232 f32::serialize(&value.movement_speed, ser)?;
233 f32::serialize(&value.movement_direction, ser)?;
234 Ok(())
235 }
236}
237
238impl Deserialize for PlayerPos {
239 type Output = Self;
240 fn deserialize(deserializer: &mut Deserializer<'_>) -> DeserializeResult<Self> {
241 let s_position = v3s32::deserialize(deserializer)?;
242 let s_speed = v3s32::deserialize(deserializer)?;
243 let s_pitch = s32::deserialize(deserializer)?;
244 let s_yaw = s32::deserialize(deserializer)?;
245 let keys_pressed = u32::deserialize(deserializer)?;
246 let s_fov = u8::deserialize(deserializer)?;
247 let wanted_range = u8::deserialize(deserializer)?;
248
249 let bits = u8::deserialize(deserializer)?;
250
251 let movement_speed = f32::deserialize(deserializer)?;
252 let movement_direction = f32::deserialize(deserializer)?;
253
254 Ok(PlayerPos {
255 position: s_position.as_v3f() / 100_f32,
256 speed: s_speed.as_v3f() / 100_f32,
257 pitch: (s_pitch as f32) / 100_f32,
258 yaw: (s_yaw as f32) / 100_f32,
259 keys_pressed,
260 fov: f32::from(s_fov) / 80_f32,
261 wanted_range,
262 camera_inverted: bits & 0b0000_0001 != 0,
263 movement_speed,
264 movement_direction,
265 })
266 }
267}
268
269#[derive(Debug, Clone, PartialEq)]
270pub struct Pair<T1, T2>(PhantomData<(T1, T2)>);
271
272impl<T1: Serialize, T2: Serialize> Serialize for Pair<T1, T2>
273where
274 <T1 as Serialize>::Input: Sized,
275 <T2 as Serialize>::Input: Sized,
276{
277 type Input = (T1::Input, T2::Input);
278 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
279 <T1 as Serialize>::serialize(&value.0, ser)?;
280 <T2 as Serialize>::serialize(&value.1, ser)?;
281 Ok(())
282 }
283}
284
285impl<T1: Deserialize, T2: Deserialize> Deserialize for Pair<T1, T2> {
286 type Output = (T1::Output, T2::Output);
287 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self::Output> {
288 Ok((
289 <T1 as Deserialize>::deserialize(deser)?,
290 <T2 as Deserialize>::deserialize(deser)?,
291 ))
292 }
293}
294
295#[derive(Debug, Clone, PartialEq)]
296pub struct MinimapModeList {
297 pub mode: u16,
298 pub vec: Vec<MinimapMode>,
299}
300
301impl Serialize for MinimapModeList {
302 type Input = Self;
303 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
304 u16::serialize(&u16::try_from(value.vec.len())?, ser)?;
307 u16::serialize(&value.mode, ser)?;
308 for mode in &value.vec {
309 MinimapMode::serialize(mode, ser)?;
310 }
311 Ok(())
312 }
313}
314
315impl Deserialize for MinimapModeList {
316 type Output = Self;
317 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
318 let count = u16::deserialize(deser)?;
319 let mode = u16::deserialize(deser)?;
320 let mut vec: Vec<MinimapMode> = Vec::with_capacity(count as usize);
321 for _ in 0..count {
322 vec.push(MinimapMode::deserialize(deser)?);
323 }
324 Ok(MinimapModeList { mode, vec })
325 }
326}
327
328#[derive(Debug, Clone, PartialEq)]
329pub struct AuthMechsBitset {
330 pub legacy_password: bool,
331 pub srp: bool,
332 pub first_srp: bool,
333}
334
335impl Serialize for AuthMechsBitset {
336 type Input = Self;
337 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
338 let mut bits: u32 = 0;
339 if value.legacy_password {
340 bits |= 1;
341 }
342 if value.srp {
343 bits |= 2;
344 }
345 if value.first_srp {
346 bits |= 4;
347 }
348 u32::serialize(&bits, ser)
349 }
350}
351
352impl Deserialize for AuthMechsBitset {
353 type Output = Self;
354 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
355 let value = u32::deserialize(deser)?;
356 Ok(AuthMechsBitset {
357 legacy_password: (value & 1) != 0,
358 srp: (value & 2) != 0,
359 first_srp: (value & 4) != 0,
360 })
361 }
362}
363
364#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
365pub struct SimpleSoundSpec {
366 pub name: String,
367 pub gain: f32,
368 pub pitch: f32,
369 pub fade: f32,
370}
371
372#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
373pub enum DrawType {
374 Normal,
375 AirLike,
376 Liquid,
377 FlowingLiquid,
378 GlassLike,
379 AllFaces,
380 AllFacesOptional,
381 TorchLike,
382 SignLike,
383 PlantLike,
384 FenceLike,
385 RailLike,
386 NodeBox,
387 GlassLikeFramed,
388 FireLike,
389 GlassLikeFramedOptional,
390 Mesh,
391 PlantLikeRooted,
392}
393
394#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
395#[expect(clippy::struct_excessive_bools, reason = "this is mandated by the API")]
396pub struct ContentFeatures {
397 pub version: u8,
398 pub name: String,
399 #[wrap(Array16<Pair<String, s16>>)]
400 pub groups: Vec<(String, s16)>,
401 pub param_type: u8,
402 pub param_type_2: u8,
403 pub drawtype: DrawType,
404 pub mesh: String,
405 pub visual_scale: f32,
406 pub unused_six: u8,
408 #[wrap(FixedArray<6, TileDef>)]
409 pub tiledef: [TileDef; 6],
410 #[wrap(FixedArray<6, TileDef>)]
411 pub tiledef_overlay: [TileDef; 6],
412 #[wrap(Array8<TileDef>)]
413 pub tiledef_special: Vec<TileDef>,
414 pub alpha_for_legacy: u8,
415 pub red: u8,
416 pub green: u8,
417 pub blue: u8,
418 pub palette_name: String,
419 pub waving: u8,
420 pub connect_sides: u8,
421 #[wrap(Array16<u16>)]
422 pub connects_to_ids: Vec<u16>,
423 pub post_effect_color: SColor,
424 pub leveled: u8,
425 pub light_propagates: u8,
426 pub sunlight_propagates: u8,
427 pub light_source: u8,
428 pub is_ground_content: bool,
429 pub walkable: bool,
430 pub pointable: bool,
431 pub diggable: bool,
432 pub climbable: bool,
433 pub buildable_to: bool,
434 pub rightclickable: bool,
435 pub damage_per_second: u32,
436 pub liquid_type_bc: u8,
437 pub liquid_alternative_flowing: String,
438 pub liquid_alternative_source: String,
439 pub liquid_viscosity: u8,
440 pub liquid_renewable: bool,
441 pub liquid_range: u8,
442 pub drowning: u8,
443 pub floodable: bool,
444 pub node_box: NodeBox,
445 pub selection_box: NodeBox,
446 pub collision_box: NodeBox,
447 pub sound_footstep: SimpleSoundSpec,
448 pub sound_dig: SimpleSoundSpec,
449 pub sound_dug: SimpleSoundSpec,
450 pub legacy_facedir_simple: bool,
451 pub legacy_wallmounted: bool,
452 pub node_dig_prediction: Option<String>,
453 pub leveled_max: Option<u8>,
454 pub alpha: Option<AlphaMode>,
455 pub move_resistance: Option<u8>,
456 pub liquid_move_physics: Option<bool>,
457}
458
459#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
460pub enum AlphaMode {
461 Blend,
462 Clip,
463 Opaque,
464 LegacyCompat,
465}
466
467#[derive(Debug, Clone, PartialEq)]
468pub struct NodeDefManager {
469 pub content_features: Vec<(u16, ContentFeatures)>,
470}
471
472impl Serialize for NodeDefManager {
476 type Input = Self;
477 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
478 u8::serialize(&1, ser)?;
480 let count: u16 = u16::try_from(value.content_features.len())?;
481 u16::serialize(&count, ser)?;
482 let string32_wrapper = ser.write_marker(4)?;
485 for (index, features) in &value.content_features {
486 u16::serialize(index, ser)?;
487 let string16_wrapper = ser.write_marker(2)?;
489 ContentFeatures::serialize(features, ser)?;
490 let len: u16 = u16::try_from(ser.marker_distance(&string16_wrapper))?;
491 ser.set_marker(string16_wrapper, &len.to_be_bytes()[..])?;
492 }
493 let len: u32 = u32::try_from(ser.marker_distance(&string32_wrapper))?;
494 ser.set_marker(string32_wrapper, &len.to_be_bytes()[..])?;
495 Ok(())
496 }
497}
498
499impl Deserialize for NodeDefManager {
500 type Output = Self;
501 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
502 let version = u8::deserialize(deser)?;
503 if version != 1 {
504 bail!(DeserializeError::InvalidValue(
505 "Bad NodeDefManager version".into(),
506 ));
507 }
508 let count: u16 = u16::deserialize(deser)?;
509 let string32_wrapper_len: u32 = u32::deserialize(deser)?;
510 let mut deser = deser.slice(string32_wrapper_len as usize)?;
512 let mut content_features: Vec<(u16, ContentFeatures)> = Vec::with_capacity(count as usize);
513 for _ in 0..count {
514 let i = u16::deserialize(&mut deser)?;
515 let string16_wrapper_len: u16 = u16::deserialize(&mut deser)?;
516 let mut inner_deser = deser.slice(string16_wrapper_len as usize)?;
517 let features = ContentFeatures::deserialize(&mut inner_deser)?;
518 content_features.push((i, features));
519 }
520 Ok(Self { content_features })
521 }
522}
523
524const MAP_BLOCKSIZE: u16 = 16;
526
527const NODECOUNT: u16 = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE;
529
530#[derive(Debug, Clone, PartialEq)]
531pub struct MapBlock {
532 pub is_underground: bool,
533 pub day_night_diff: bool,
534 pub generated: bool,
535 pub lighting_complete: Option<u16>,
536 pub nodes: MapNodesBulk,
537 pub node_metadata: NodeMetadataList, }
539
540impl Serialize for MapBlock {
541 type Input = Self;
549 fn serialize<S: Serializer>(value: &Self::Input, serializer: &mut S) -> SerializeResult {
550 let ver = serializer.context().ser_fmt;
551 let mut tmp_ser = VecSerializer::new(serializer.context(), 0x8000);
552 let ser = &mut tmp_ser;
553 let header = MapBlockHeader {
554 is_underground: value.is_underground,
555 day_night_diff: value.day_night_diff,
556 generated: value.generated,
557 lighting_complete: value.lighting_complete,
558 };
559 MapBlockHeader::serialize(&header, ser)?;
560 if ver >= 29 {
561 MapNodesBulk::serialize(&value.nodes, ser)?;
562 } else {
563 let mut inner = VecSerializer::new(ser.context(), 0x8000);
565 MapNodesBulk::serialize(&value.nodes, &mut inner)?;
566 let compressed = compress_zlib(&inner.take());
567 ser.write_bytes(&compressed)?;
568 }
569 if ver >= 29 {
570 NodeMetadataList::serialize(&value.node_metadata, ser)?;
571 } else {
572 let mut inner = VecSerializer::new(ser.context(), 0x8000);
574 NodeMetadataList::serialize(&value.node_metadata, &mut inner)?;
575 let compressed = compress_zlib(&inner.take());
576 ser.write_bytes(&compressed)?;
577 }
578 if ver >= 29 {
579 let tmp = tmp_ser.take();
581 zstd_compress(&tmp, |chunk| serializer.write_bytes(chunk))?;
582 } else {
583 let tmp = tmp_ser.take();
585 serializer.write_bytes(&tmp)?;
586 }
587 Ok(())
588 }
589}
590
591#[derive(Debug)]
595struct MapBlockHeader {
596 pub is_underground: bool,
597 pub day_night_diff: bool,
598 pub generated: bool,
599 pub lighting_complete: Option<u16>,
600}
601
602impl Serialize for MapBlockHeader {
603 type Input = Self;
604 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
605 let mut flags: u8 = 0;
606 if value.is_underground {
607 flags |= 0x1;
608 }
609 if value.day_night_diff {
610 flags |= 0x2;
611 }
612 if !value.generated {
613 flags |= 0x8;
614 }
615 u8::serialize(&flags, ser)?;
616 if ser.context().ser_fmt >= 27 {
617 if let Some(lighting_complete) = value.lighting_complete {
618 u16::serialize(&lighting_complete, ser)?;
619 } else {
620 bail!("lighting_complete must be set for ver >= 27");
621 }
622 }
623 u8::serialize(&2, ser)?; u8::serialize(&2, ser)?; Ok(())
626 }
627}
628
629impl Deserialize for MapBlockHeader {
630 type Output = Self;
631 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
632 let flags = u8::deserialize(deser)?;
633 if flags != (flags & (0x1 | 0x2 | 0x8)) {
634 bail!(DeserializeError::InvalidValue(
635 "Invalid MapBlock flags".into(),
636 ));
637 }
638 #[expect(clippy::if_then_some_else_none, reason = "`?`-operator prohibits this")]
639 let lighting_complete = if deser.context().ser_fmt >= 27 {
640 Some(u16::deserialize(deser)?)
641 } else {
642 None
643 };
644 let content_width = u8::deserialize(deser)?;
645 let params_width = u8::deserialize(deser)?;
646 if content_width != 2 || params_width != 2 {
647 bail!(DeserializeError::InvalidValue(
648 "Corrupt MapBlock: content_width and params_width not both 2".into(),
649 ));
650 }
651 Ok(Self {
652 is_underground: (flags & 0x1) != 0,
653 day_night_diff: (flags & 0x2) != 0,
654 generated: (flags & 0x8) == 0,
655 lighting_complete,
656 })
657 }
658}
659
660impl Deserialize for MapBlock {
661 type Output = Self;
662 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
663 let ver = deser.context().ser_fmt;
664 if ver < 28 {
665 bail!("Unsupported ser fmt");
666 }
667 if ver >= 29 {
670 let mut tmp: Vec<u8> = Vec::new();
671 let bytes_taken = zstd_decompress(deser.peek_all(), |chunk| {
673 tmp.extend_from_slice(chunk);
674 Ok(())
675 })?;
676 deser.take(bytes_taken)?;
677 let deser = &mut Deserializer::new(deser.context(), &tmp);
678 let header = MapBlockHeader::deserialize(deser)?;
679 let nodes = MapNodesBulk::deserialize(deser)?;
680 let node_metadata = NodeMetadataList::deserialize(deser)?;
681 Ok(Self {
682 is_underground: header.is_underground,
683 day_night_diff: header.day_night_diff,
684 generated: header.generated,
685 lighting_complete: header.lighting_complete,
686 nodes,
687 node_metadata,
688 })
689 } else {
690 let header = MapBlockHeader::deserialize(deser)?;
691 let (consumed1, nodes_raw) = decompress_zlib(deser.peek_all())?;
692 deser.take(consumed1)?;
693 let nodes = {
694 let mut tmp = Deserializer::new(deser.context(), &nodes_raw);
695 MapNodesBulk::deserialize(&mut tmp)?
696 };
697 let (consumed2, metadata_raw) = decompress_zlib(deser.peek_all())?;
698 deser.take(consumed2)?;
699 let node_metadata = {
700 let mut tmp = Deserializer::new(deser.context(), &metadata_raw);
701 NodeMetadataList::deserialize(&mut tmp)?
702 };
703 Ok(Self {
704 is_underground: header.is_underground,
705 day_night_diff: header.day_night_diff,
706 generated: header.generated,
707 lighting_complete: header.lighting_complete,
708 nodes,
709 node_metadata,
710 })
711 }
712 }
713}
714
715#[derive(Debug, Clone, PartialEq)]
718pub struct MapNodesBulk {
719 pub nodes: [MapNode; NODECOUNT as usize],
720}
721
722impl Serialize for MapNodesBulk {
723 type Input = Self;
724 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
725 let nodecount = NODECOUNT as usize;
726 ser.write(2 * nodecount, |buf| {
728 assert_eq!(buf.len(), 2 * nodecount, "size mismatch");
729 for index in 0..nodecount {
730 let bytes = value.nodes[index].param0.to_be_bytes();
731 buf[2 * index] = bytes[0];
732 buf[2 * index + 1] = bytes[1];
733 }
734 })?;
735 ser.write(nodecount, |buf| {
737 assert_eq!(buf.len(), nodecount, "size mismatch");
738 #[expect(
739 clippy::needless_range_loop,
740 reason = "// TODO transform into iterator"
741 )]
742 for index in 0..nodecount {
743 buf[index] = value.nodes[index].param1;
744 }
745 })?;
746 ser.write(nodecount, |buf| {
748 assert_eq!(buf.len(), nodecount, "size mismatch");
749 #[expect(
750 clippy::needless_range_loop,
751 reason = "// TODO transform into iterator"
752 )]
753 for i in 0..nodecount {
754 buf[i] = value.nodes[i].param2;
755 }
756 })?;
757 Ok(())
758 }
759}
760
761impl Deserialize for MapNodesBulk {
762 type Output = Self;
763 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
764 let nodecount = NODECOUNT as usize;
765 let data = deser.take(4 * nodecount)?;
766 let mut nodes: Vec<MapNode> = Vec::with_capacity(nodecount);
767 let param1_offset = 2 * nodecount;
768 let param2_offset = 3 * nodecount;
769 for i in 0..nodecount {
770 nodes.push(MapNode {
771 param0: u16::from_be_bytes(data[2 * i..2 * i + 2].try_into().unwrap()),
772 param1: data[param1_offset + i],
773 param2: data[param2_offset + i],
774 });
775 }
776 Ok(Self {
777 nodes: match nodes.try_into() {
778 Ok(value) => value,
779 Err(_) => bail!("Bug in MapNodesBulk"),
780 },
781 })
782 }
783}
784
785#[derive(Debug, Default, Clone, Copy, PartialEq, LuantiSerialize, LuantiDeserialize)]
788pub struct MapNode {
789 pub param0: u16,
790 pub param1: u8,
791 pub param2: u8,
792}
793
794#[derive(Debug, Clone, PartialEq)]
795pub struct NodeMetadataList {
796 pub metadata: Vec<(BlockPos, NodeMetadata)>,
797}
798
799impl Serialize for NodeMetadataList {
800 type Input = Self;
801 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
802 if value.metadata.is_empty() {
803 u8::serialize(&0, ser)?; return Ok(());
805 }
806 u8::serialize(&2, ser)?; <Array16<Pair<BlockPos, NodeMetadata>> as Serialize>::serialize(&value.metadata, ser)?;
808 Ok(())
809 }
810}
811
812impl Deserialize for NodeMetadataList {
813 type Output = Self;
814 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
815 let ver = u8::deserialize(deser)?;
816 if ver == 0 {
817 Ok(Self {
818 metadata: Vec::new(),
819 })
820 } else if ver == 2 {
821 Ok(Self {
822 metadata: <Array16<Pair<BlockPos, NodeMetadata>> as Deserialize>::deserialize(
823 deser,
824 )?,
825 })
826 } else {
827 bail!(DeserializeError::InvalidValue(
828 "Invalid NodeMetadataList version".into(),
829 ))
830 }
831 }
832}
833
834#[derive(Debug, Clone, PartialEq)]
835pub struct AbsNodeMetadataList {
836 pub metadata: Vec<(AbsBlockPos, NodeMetadata)>,
837}
838
839impl Serialize for AbsNodeMetadataList {
840 type Input = Self;
841 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
842 if value.metadata.is_empty() {
843 u8::serialize(&0, ser)?; return Ok(());
845 }
846 u8::serialize(&2, ser)?; <Array16<Pair<AbsBlockPos, NodeMetadata>> as Serialize>::serialize(&value.metadata, ser)?;
848 Ok(())
849 }
850}
851
852impl Deserialize for AbsNodeMetadataList {
853 type Output = Self;
854 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
855 let ver = u8::deserialize(deser)?;
856 if ver == 0 {
857 Ok(Self {
858 metadata: Vec::new(),
859 })
860 } else if ver == 2 {
861 Ok(Self {
862 metadata: <Array16<Pair<AbsBlockPos, NodeMetadata>> as Deserialize>::deserialize(
863 deser,
864 )?,
865 })
866 } else {
867 bail!(DeserializeError::InvalidValue(
868 "Invalid AbsNodeMetadataList version".into(),
869 ))
870 }
871 }
872}
873
874#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
875pub struct AbsBlockPos {
876 pos: v3s16,
877}
878
879#[derive(Debug, Clone, PartialEq)]
882pub struct BlockPos {
883 pub raw: u16,
884}
885
886impl BlockPos {
887 #[must_use]
888 pub fn new(x: s16, y: s16, z: s16) -> Self {
889 let valid = 0..(MAP_BLOCKSIZE as s16);
890 assert!(
891 valid.contains(&x) && valid.contains(&y) && valid.contains(&z),
892 "//TODO add proper error message"
893 );
894 let x = x as u16;
895 let y = y as u16;
896 let z = z as u16;
897 Self {
898 raw: (MAP_BLOCKSIZE * z + y) * MAP_BLOCKSIZE + x,
899 }
900 }
901
902 #[must_use]
903 pub fn from_xyz(pos: v3s16) -> Self {
904 Self::new(pos.x, pos.y, pos.z)
905 }
906
907 #[must_use]
908 pub fn to_xyz(&self) -> v3s16 {
909 let x = self.raw % 16;
910 let y = (self.raw / 16) % 16;
911 let z = (self.raw / 256) % 16;
912 v3s16::new(x as i16, y as i16, z as i16)
913 }
914}
915
916impl Serialize for BlockPos {
917 type Input = Self;
918 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
919 u16::serialize(&value.raw, ser)?;
920 Ok(())
921 }
922}
923
924impl Deserialize for BlockPos {
925 type Output = Self;
926 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
927 let raw = u16::deserialize(deser)?;
928 if raw >= 4096 {
929 bail!(DeserializeError::InvalidValue("Invalid BlockPos".into(),))
930 }
931 Ok(Self { raw })
932 }
933}
934
935#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
936pub struct NodeMetadata {
937 #[wrap(Array32<StringVar>)]
938 pub stringvars: Vec<StringVar>,
939 pub inventory: Inventory,
940}
941
942#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
943pub struct StringVar {
944 pub name: String,
945 #[wrap(BinaryData32)]
946 pub value: Vec<u8>,
947 pub is_private: bool,
948}
949
950#[derive(Debug, Clone, PartialEq)]
951pub struct Inventory {
952 pub entries: Vec<InventoryEntry>,
953}
954
955#[derive(Debug, Clone, PartialEq)]
956pub enum InventoryEntry {
957 KeepList(String),
959 Update(InventoryList),
961}
962
963impl Serialize for Inventory {
967 type Input = Self;
968 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
969 for entry in &value.entries {
970 match entry {
971 InventoryEntry::KeepList(list_name) => {
972 ser.write_bytes(b"KeepList ")?;
975 ser.write_bytes(list_name.as_bytes())?;
976 ser.write_bytes(b"\n")?;
977 }
978 InventoryEntry::Update(list) => {
979 InventoryList::serialize(list, ser)?;
981 }
982 }
983 }
984 ser.write_bytes(b"EndInventory\n")?;
985 Ok(())
986 }
987}
988
989impl Deserialize for Inventory {
990 type Output = Self;
991 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
992 let mut result = Self {
993 entries: Vec::new(),
994 };
995 while deser.has_remaining() {
996 let line = deser.peek_line()?;
998 let words = split_by_whitespace(line);
999 if words.is_empty() {
1000 deser.take_line()?;
1001 continue;
1002 }
1003 let name = words[0];
1004 if name == b"EndInventory" || name == b"End" {
1005 deser.take_line()?;
1007 return Ok(result);
1008 } else if name == b"List" {
1009 result
1011 .entries
1012 .push(InventoryEntry::Update(InventoryList::deserialize(deser)?));
1013 } else if name == b"KeepList" {
1014 if words.len() < 2 {
1015 bail!(DeserializeError::InvalidValue(
1016 "KeepList missing name".into(),
1017 ));
1018 }
1019 match std::str::from_utf8(words[1]) {
1020 Ok(str) => result.entries.push(InventoryEntry::KeepList(str.into())),
1021 Err(_) => {
1022 bail!(DeserializeError::InvalidValue(
1023 "KeepList name is invalid UTF8".into(),
1024 ))
1025 }
1026 }
1027 deser.take_line()?;
1029 } else {
1030 deser.take_line()?;
1032 }
1033 }
1034 bail!(DeserializeError::Eof("Inventory::deserialize(_)".into()))
1036 }
1037}
1038
1039#[derive(Debug, Clone, PartialEq)]
1040pub struct InventoryList {
1041 pub name: String,
1042 pub width: u32,
1043 pub items: Vec<ItemStackUpdate>,
1044}
1045
1046#[derive(Debug, Clone, PartialEq)]
1047pub enum ItemStackUpdate {
1048 Empty,
1049 Keep, Item(ItemStack),
1051}
1052
1053impl Serialize for InventoryList {
1054 type Input = Self;
1055 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
1056 ser.write_bytes(b"List ")?;
1058 ser.write_bytes(value.name.as_bytes())?;
1059 ser.write_bytes(b" ")?;
1060 ser.write_bytes(value.items.len().to_string().as_bytes())?;
1061 ser.write_bytes(b"\n")?;
1062
1063 ser.write_bytes(b"Width ")?;
1065 ser.write_bytes(value.width.to_string().as_bytes())?;
1066 ser.write_bytes(b"\n")?;
1067
1068 for item in &value.items {
1069 match item {
1070 ItemStackUpdate::Empty => ser.write_bytes(b"Empty\n")?,
1071 ItemStackUpdate::Keep => ser.write_bytes(b"Keep\n")?,
1072 ItemStackUpdate::Item(item_stack) => {
1073 ItemStack::serialize(item_stack, ser)?;
1075 }
1076 }
1077 }
1078 ser.write_bytes(b"EndInventoryList\n")?;
1079 Ok(())
1080 }
1081}
1082
1083impl Deserialize for InventoryList {
1084 type Output = Self;
1085 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
1086 let line = deser.take_line()?;
1088 let words = split_by_whitespace(line);
1089 if words.len() != 3 || words[0] != b"List" {
1090 bail!(DeserializeError::InvalidValue("Broken List tag".into(),));
1091 }
1092 let list_name = std::str::from_utf8(words[1])?;
1093 let _count: u32 = stoi(words[2])?;
1094 let mut result = Self {
1095 name: list_name.into(),
1096 width: 0,
1097 items: Vec::new(),
1098 };
1099 while deser.has_remaining() {
1100 let peeked_line = deser.peek_line()?;
1102 let peeked_words = split_by_whitespace(peeked_line);
1103 if peeked_words.is_empty() {
1104 deser.take_line()?;
1105 continue;
1106 }
1107 let name = peeked_words[0];
1108 if name == b"EndInventoryList" || name == b"end" {
1109 deser.take_line()?;
1110 return Ok(result);
1111 } else if name == b"Width" {
1112 if peeked_words.len() < 2 {
1113 bail!(DeserializeError::InvalidValue("Width value missing".into(),));
1114 }
1115 result.width = stoi(peeked_words[1])?;
1116 deser.take_line()?;
1117 } else if name == b"Item" {
1118 result
1120 .items
1121 .push(ItemStackUpdate::Item(ItemStack::deserialize(deser)?));
1122 } else if name == b"Empty" {
1123 result.items.push(ItemStackUpdate::Empty);
1124 deser.take_line()?;
1125 } else if name == b"Keep" {
1126 result.items.push(ItemStackUpdate::Keep);
1127 deser.take_line()?;
1128 } else {
1129 deser.take_line()?;
1131 }
1132 }
1133 bail!(DeserializeError::Eof(
1134 "InventoryList::deserialize(_)".into()
1135 ))
1136 }
1137}
1138
1139#[derive(Debug, Clone, PartialEq)]
1141pub struct ItemStack {
1142 pub name: String,
1143 pub count: u16,
1144 pub wear: u16,
1145 pub metadata: ItemStackMetadata,
1146}
1147
1148impl Serialize for ItemStack {
1149 type Input = Self;
1150 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
1151 ser.write_bytes(b"Item ")?;
1153 serialize_json_string_if_needed(value.name.as_bytes(), |chunk| ser.write_bytes(chunk))?;
1154
1155 let mut parts = 1;
1156 if !value.metadata.string_vars.is_empty() {
1157 parts = 4;
1158 } else if value.wear != 0 {
1159 parts = 3;
1160 } else if value.count != 1 {
1161 parts = 2;
1162 }
1163
1164 if parts >= 2 {
1165 ser.write_bytes(b" ")?;
1166 ser.write_bytes(value.count.to_string().as_bytes())?;
1167 }
1168 if parts >= 3 {
1169 ser.write_bytes(b" ")?;
1170 ser.write_bytes(value.wear.to_string().as_bytes())?;
1171 }
1172 if parts >= 4 {
1173 ser.write_bytes(b" ")?;
1174 ItemStackMetadata::serialize(&value.metadata, ser)?;
1175 }
1176 ser.write_bytes(b"\n")?;
1177 Ok(())
1178 }
1179}
1180
1181impl Deserialize for ItemStack {
1182 type Output = Self;
1183 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
1184 let line = deser.take_line()?;
1186 let err = DeserializeError::InvalidValue("Truncated Item line".into());
1187 let (first_word, line) = next_word(line).ok_or(err)?;
1188 if first_word != b"Item" {
1189 bail!(DeserializeError::InvalidValue("Invalid Item line".into(),));
1190 }
1191 let line = skip_whitespace(line);
1192 let (name, skip) = deserialize_json_string_if_needed(line)?;
1193 let line = skip_whitespace(&line[skip..]);
1194
1195 let mut result = Self {
1196 name: std::str::from_utf8(&name)?.into(),
1197 count: 1,
1198 wear: 0,
1199 metadata: ItemStackMetadata {
1200 string_vars: Vec::new(),
1201 },
1202 };
1203 if let Some((count_str, line)) = next_word(line) {
1204 result.count = stoi(count_str)?;
1205 if let Some((wear_str, line)) = next_word(line) {
1206 result.wear = stoi(wear_str)?;
1207 let line = skip_whitespace(line);
1208 if !line.is_empty() {
1209 let mut tmp_deser = Deserializer::new(deser.context(), line);
1210 result.metadata = ItemStackMetadata::deserialize(&mut tmp_deser)?;
1211 }
1212 }
1213 }
1214 Ok(result)
1215 }
1216}
1217
1218#[derive(Debug, Clone, PartialEq)]
1220pub struct ItemStackMetadata {
1221 pub string_vars: Vec<(ByteString, ByteString)>,
1222}
1223
1224const DESERIALIZE_START: &[u8; 1] = b"\x01";
1225const DESERIALIZE_KV_DELIM: &[u8; 1] = b"\x02";
1226const DESERIALIZE_PAIR_DELIM: &[u8; 1] = b"\x03";
1227
1228impl Serialize for ItemStackMetadata {
1229 type Input = Self;
1230 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
1231 let mut buf: Vec<u8> = Vec::new();
1232 buf.extend(DESERIALIZE_START);
1233 for (key, val) in &value.string_vars {
1234 if !key.is_empty() || !val.is_empty() {
1235 buf.extend(key.as_bytes());
1236 buf.extend(DESERIALIZE_KV_DELIM);
1237 buf.extend(val.as_bytes());
1238 buf.extend(DESERIALIZE_PAIR_DELIM);
1239 }
1240 }
1241 serialize_json_string_if_needed(&buf, |chunk| ser.write_bytes(chunk))?;
1242 Ok(())
1243 }
1244}
1245
1246impl Deserialize for ItemStackMetadata {
1247 type Output = Self;
1248 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
1249 let (raw, count) = deserialize_json_string_if_needed(deser.peek_all())?;
1250 deser.take(count)?;
1251 let mut result = Self {
1252 string_vars: Vec::new(),
1253 };
1254 if raw.is_empty() {
1255 return Ok(result);
1256 }
1257 if raw[0] != DESERIALIZE_START[0] {
1258 bail!(DeserializeError::InvalidValue(
1259 "ItemStackMetadata bad start".into(),
1260 ));
1261 }
1262 let mut raw = &raw[1..];
1263 while !raw.is_empty() {
1265 let kv_delim_pos = raw
1266 .iter()
1267 .position(|ch| *ch == DESERIALIZE_KV_DELIM[0])
1268 .unwrap_or(raw.len());
1269 let name = &raw[..kv_delim_pos];
1270 raw = &raw[kv_delim_pos..];
1271 if !raw.is_empty() {
1272 raw = &raw[1..];
1273 }
1274 let pair_delim_pos = raw
1275 .iter()
1276 .position(|ch| *ch == DESERIALIZE_PAIR_DELIM[0])
1277 .unwrap_or(raw.len());
1278 let var = &raw[..pair_delim_pos];
1279 raw = &raw[pair_delim_pos..];
1280 if !raw.is_empty() {
1281 raw = &raw[1..];
1282 }
1283 result.string_vars.push((name.into(), var.into()));
1284 }
1285 Ok(result)
1286 }
1287}
1288
1289#[derive(Debug, Default, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
1290pub struct RangedParameter<T: Serialize + Deserialize>
1291where
1292 T: Serialize<Input = T>,
1293 T: Deserialize<Output = T>,
1294{
1295 pub min: T,
1296 pub max: T,
1297 pub bias: f32,
1298}
1299
1300#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
1301pub struct Lighting {
1302 pub shadow_intensity: f32,
1303 pub saturation: f32,
1304 pub exposure: AutoExposure,
1305
1306 pub volumetric_light_strength: f32,
1307 pub shadow_tint: SColor,
1308 pub bloom_intensity: f32,
1309 pub bloom_strength_factor: f32,
1310 pub bloom_radius: f32,
1311}
1312
1313#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
1314pub struct AutoExposure {
1315 pub luminance_min: f32,
1316 pub luminance_max: f32,
1317 pub exposure_correction: f32,
1318 pub speed_dark_bright: f32,
1319 pub speed_bright_dark: f32,
1320 pub center_weight_power: f32,
1321}
1322
1323#[derive(Debug, Clone, PartialEq)]
1324pub enum HudSetParam {
1325 SetHotBarItemCount(s32),
1326 SetHotBarImage(String),
1327 SetHotBarSelectedImage(String),
1328}
1329
1330impl Serialize for HudSetParam {
1331 type Input = Self;
1332 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
1333 #![allow(clippy::enum_glob_use, reason = "improves readability")]
1334 use HudSetParam::*;
1335 let param: u16 = match value {
1336 SetHotBarItemCount(_) => 1,
1337 SetHotBarImage(_) => 2,
1338 SetHotBarSelectedImage(_) => 3,
1339 };
1340 u16::serialize(¶m, ser)?;
1341 match value {
1342 SetHotBarItemCount(value) => {
1343 u16::serialize(&4, ser)?;
1345 s32::serialize(value, ser)?;
1346 }
1347 SetHotBarImage(value) => String::serialize(value, ser)?,
1348 SetHotBarSelectedImage(value) => String::serialize(value, ser)?,
1349 };
1350 Ok(())
1351 }
1352}
1353
1354impl Deserialize for HudSetParam {
1355 type Output = Self;
1356 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
1357 #[allow(clippy::enum_glob_use, reason = "improves readability")]
1358 use HudSetParam::*;
1359 let param = u16::deserialize(deser)?;
1360 Ok(match param {
1361 1 => {
1362 let size = u16::deserialize(deser)?;
1363 if size != 4 {
1364 bail!("Invalid size in SetHotBarItemCount: {}", size);
1365 }
1366 SetHotBarItemCount(s32::deserialize(deser)?)
1367 }
1368 2 => SetHotBarImage(String::deserialize(deser)?),
1369 3 => SetHotBarSelectedImage(String::deserialize(deser)?),
1370 _ => bail!("Invalid HudSetParam param: {}", param),
1371 })
1372 }
1373}
1374
1375#[derive(Debug, Clone, PartialEq)]
1376#[expect(
1377 clippy::struct_excessive_bools,
1378 reason = "// TODO rewrite using a crate for flags or bit-fields"
1379)]
1380pub struct HudFlags {
1381 pub hotbar_visible: bool,
1382 pub healthbar_visible: bool,
1383 pub crosshair_visible: bool,
1384 pub wielditem_visible: bool,
1385 pub breathbar_visible: bool,
1386 pub minimap_visible: bool,
1387 pub minimap_radar_visible: bool,
1388 pub basic_debug: bool,
1389 pub chat_visible: bool,
1390}
1391
1392impl HudFlags {
1393 #[must_use]
1394 pub fn to_u32(&self) -> u32 {
1395 #![expect(clippy::identity_op, reason = "for symmetry")]
1396 let mut flags: u32 = 0;
1397 flags |= u32::from(self.hotbar_visible) << 0;
1398 flags |= u32::from(self.healthbar_visible) << 1;
1399 flags |= u32::from(self.crosshair_visible) << 2;
1400 flags |= u32::from(self.wielditem_visible) << 3;
1401 flags |= u32::from(self.breathbar_visible) << 4;
1402 flags |= u32::from(self.minimap_visible) << 5;
1403 flags |= u32::from(self.minimap_radar_visible) << 6;
1404 flags |= u32::from(self.basic_debug) << 7;
1405 flags |= u32::from(self.chat_visible) << 8;
1406 flags
1407 }
1408
1409 #[must_use]
1410 pub fn from_u32(flags: u32) -> Self {
1411 Self {
1412 hotbar_visible: (flags & (1 << 0)) != 0,
1413 healthbar_visible: (flags & (1 << 1)) != 0,
1414 crosshair_visible: (flags & (1 << 2)) != 0,
1415 wielditem_visible: (flags & (1 << 3)) != 0,
1416 breathbar_visible: (flags & (1 << 4)) != 0,
1417 minimap_visible: (flags & (1 << 5)) != 0,
1418 minimap_radar_visible: (flags & (1 << 6)) != 0,
1419 basic_debug: (flags & (1 << 7)) != 0,
1420 chat_visible: (flags & (1 << 8)) != 0,
1421 }
1422 }
1423}
1424
1425impl Serialize for HudFlags {
1426 type Input = Self;
1427 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
1428 let value = value.to_u32();
1429 u32::serialize(&value, ser)
1430 }
1431}
1432
1433impl Deserialize for HudFlags {
1434 type Output = Self;
1435 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
1436 let value = u32::deserialize(deser)?;
1437 if (value & !0b1_1111_1111) != 0 {
1438 bail!("Invalid HudFlags: {}", value);
1439 }
1440 Ok(HudFlags::from_u32(value))
1441 }
1442}
1443
1444#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
1445pub enum InteractAction {
1446 StartDigging,
1447 StopDigging,
1448 DiggingCompleted,
1449 Place,
1450 Use,
1451 Activate,
1452}
1453
1454#[derive(Debug, Clone, PartialEq)]
1455#[expect(
1456 variant_size_differences,
1457 reason = "// TODO review whether this is actually a problem"
1458)]
1459pub enum PointedThing {
1460 Nothing,
1461 Node {
1462 under_surface: v3s16,
1463 above_surface: v3s16,
1464 },
1465 Object {
1466 object_id: u16,
1467 },
1468}
1469
1470impl Serialize for PointedThing {
1471 type Input = Self;
1472 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
1473 u8::serialize(&0, ser)?;
1475
1476 let typ: u8 = match value {
1477 PointedThing::Nothing => 0,
1478 PointedThing::Node { .. } => 1,
1479 PointedThing::Object { .. } => 2,
1480 };
1481 u8::serialize(&typ, ser)?;
1482
1483 match value {
1484 PointedThing::Nothing => (),
1485 PointedThing::Node {
1486 under_surface,
1487 above_surface,
1488 } => {
1489 v3s16::serialize(under_surface, ser)?;
1490 v3s16::serialize(above_surface, ser)?;
1491 }
1492 PointedThing::Object { object_id } => {
1493 u16::serialize(object_id, ser)?;
1494 }
1495 }
1496 Ok(())
1497 }
1498}
1499
1500impl Deserialize for PointedThing {
1501 type Output = Self;
1502 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
1503 let ver = u8::deserialize(deser)?;
1504 if ver != 0 {
1505 bail!("Invalid PointedThing version: {}", ver);
1506 }
1507 let typ = u8::deserialize(deser)?;
1508 Ok(match typ {
1509 0 => PointedThing::Nothing,
1510 1 => PointedThing::Node {
1511 under_surface: v3s16::deserialize(deser)?,
1512 above_surface: v3s16::deserialize(deser)?,
1513 },
1514 2 => PointedThing::Object {
1515 object_id: u16::deserialize(deser)?,
1516 },
1517 _ => bail!("Invalid PointedThing type: {}", typ),
1518 })
1519 }
1520}
1521
1522#[derive(Debug, Clone, PartialEq)]
1523pub enum InventoryAction {
1524 Move {
1525 count: u16,
1526 from_inv: InventoryLocation,
1527 from_list: String,
1528 from_i: s16,
1529 to_inv: InventoryLocation,
1530 to_list: String,
1531 to_i: Option<s16>,
1532 },
1533 Craft {
1534 count: u16,
1535 craft_inv: InventoryLocation,
1536 },
1537 Drop {
1538 count: u16,
1539 from_inv: InventoryLocation,
1540 from_list: String,
1541 from_i: s16,
1542 },
1543}
1544
1545impl Serialize for InventoryAction {
1546 type Input = Self;
1547 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
1548 match value {
1549 InventoryAction::Move {
1550 count,
1551 from_inv,
1552 from_list,
1553 from_i,
1554 to_inv,
1555 to_list,
1556 to_i,
1557 } => {
1558 if to_i.is_some() {
1559 ser.write_bytes(b"Move ")?;
1560 } else {
1561 ser.write_bytes(b"MoveSomewhere ")?;
1562 }
1563 ser.write_bytes(itos!(count))?;
1564 ser.write_bytes(b" ")?;
1565 InventoryLocation::serialize(from_inv, ser)?;
1566 ser.write_bytes(b" ")?;
1567 ser.write_bytes(from_list.as_bytes())?;
1568 ser.write_bytes(b" ")?;
1569 ser.write_bytes(itos!(from_i))?;
1570 ser.write_bytes(b" ")?;
1571 InventoryLocation::serialize(to_inv, ser)?;
1572 ser.write_bytes(b" ")?;
1573 ser.write_bytes(to_list.as_bytes())?;
1574 if let Some(to_i) = to_i {
1575 ser.write_bytes(b" ")?;
1576 ser.write_bytes(itos!(to_i))?;
1577 }
1578 }
1579 InventoryAction::Craft { count, craft_inv } => {
1580 ser.write_bytes(b"Craft ")?;
1581 ser.write_bytes(itos!(count))?;
1582 ser.write_bytes(b" ")?;
1583 InventoryLocation::serialize(craft_inv, ser)?;
1584 ser.write_bytes(b" ")?;
1586 }
1587 InventoryAction::Drop {
1588 count,
1589 from_inv,
1590 from_list,
1591 from_i,
1592 } => {
1593 ser.write_bytes(b"Drop ")?;
1594 ser.write_bytes(itos!(count))?;
1595 ser.write_bytes(b" ")?;
1596 InventoryLocation::serialize(from_inv, ser)?;
1597 ser.write_bytes(b" ")?;
1598 ser.write_bytes(from_list.as_bytes())?;
1599 ser.write_bytes(b" ")?;
1600 ser.write_bytes(itos!(from_i))?;
1601 }
1602 }
1603 Ok(())
1604 }
1605}
1606
1607impl Deserialize for InventoryAction {
1608 type Output = Self;
1609 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
1610 let word = deser.take_word(true);
1611 if word == b"Move" || word == b"MoveSomewhere" {
1612 Ok(InventoryAction::Move {
1613 count: stoi(deser.take_word(true))?,
1614 from_inv: InventoryLocation::deserialize(deser)?,
1615 from_list: std::str::from_utf8(deser.take_word(true))?.to_owned(),
1616 from_i: stoi(deser.take_word(true))?,
1617 to_inv: InventoryLocation::deserialize(deser)?,
1618 to_list: std::str::from_utf8(deser.take_word(true))?.to_owned(),
1619 #[expect(clippy::if_then_some_else_none, reason = "`?`-operator prohibits this")]
1620 to_i: if word == b"Move" {
1621 Some(stoi(deser.take_word(true))?)
1622 } else {
1623 None
1624 },
1625 })
1626 } else if word == b"Drop" {
1627 Ok(InventoryAction::Drop {
1628 count: stoi(deser.take_word(true))?,
1629 from_inv: InventoryLocation::deserialize(deser)?,
1630 from_list: std::str::from_utf8(deser.take_word(true))?.to_owned(),
1631 from_i: stoi(deser.take_word(true))?,
1632 })
1633 } else if word == b"Craft" {
1634 Ok(InventoryAction::Craft {
1635 count: stoi(deser.take_word(true))?,
1636 craft_inv: InventoryLocation::deserialize(deser)?,
1637 })
1638 } else {
1639 bail!("Invalid InventoryAction kind");
1640 }
1641 }
1642}
1643
1644#[derive(Debug, Clone, PartialEq)]
1645pub enum InventoryLocation {
1646 Undefined,
1647 CurrentPlayer,
1648 Player { name: String },
1649 NodeMeta { pos: v3s16 },
1650 Detached { name: String },
1651}
1652
1653impl Serialize for InventoryLocation {
1654 type Input = Self;
1655 fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
1656 match value {
1657 InventoryLocation::Undefined => ser.write_bytes(b"undefined")?,
1658 InventoryLocation::CurrentPlayer => ser.write_bytes(b"current_player")?,
1659 InventoryLocation::Player { name } => {
1660 ser.write_bytes(b"player:")?;
1661 ser.write_bytes(name.as_bytes())?;
1662 }
1663 InventoryLocation::NodeMeta { pos } => {
1664 ser.write_bytes(format!("nodemeta:{},{},{}", pos.x, pos.y, pos.z).as_bytes())?;
1665 }
1666 InventoryLocation::Detached { name } => {
1667 ser.write_bytes(b"detached:")?;
1668 ser.write_bytes(name.as_bytes())?;
1669 }
1670 }
1671 Ok(())
1672 }
1673}
1674
1675impl Deserialize for InventoryLocation {
1676 type Output = Self;
1677 fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
1678 let word = deser.take_word(true);
1679 if word == b"undefined" {
1680 Ok(InventoryLocation::Undefined)
1681 } else if word == b"current_player" {
1682 Ok(InventoryLocation::CurrentPlayer)
1683 } else if word.starts_with(b"player:") {
1684 Ok(InventoryLocation::Player {
1685 name: std::str::from_utf8(&word[7..])?.into(),
1686 })
1687 } else if word.starts_with(b"nodemeta:") {
1688 let coords: Vec<&[u8]> = word[9..].split(|&ch| ch == b',').collect();
1690 if coords.len() != 3 {
1691 bail!("Corrupted nodemeta InventoryLocation");
1692 }
1693 let mut xyz = [0_i16; 3];
1694 for (i, &n) in coords.iter().enumerate() {
1695 xyz[i] = stoi(n)?;
1696 }
1697 let pos = v3s16::new(xyz[0], xyz[1], xyz[2]);
1698 Ok(InventoryLocation::NodeMeta { pos })
1699 } else if word.starts_with(b"detached:") {
1700 Ok(InventoryLocation::Detached {
1701 name: std::str::from_utf8(&word[9..])?.into(),
1702 })
1703 } else {
1704 Err(anyhow!("Unknown InventoryLocation: {:?}", word))
1705 }
1706 }
1707}