Skip to main content

mcproto_types/debug/
data.rs

1//! Typed payloads shared by debug subscription events and updates.
2
3use std::{fmt, io::Read};
4
5use mcproto_codec::error::{CodecError, CodecKind};
6
7use crate::{
8    Boolean, Double, Float, Int, Position, PrefixedArray, PrefixedOptional, PrefixedString,
9    ProtocolEnum, TypeCodec, TypeStructCodec, VarInt,
10    basic::{decode_prefixed_string, encode_prefixed_string},
11};
12
13/// Numeric discriminator for a debug subscription payload.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ProtocolEnum)]
15#[protocol_enum(repr = VarInt)]
16pub enum DebugSubscriptionType {
17    /// Dedicated server tick-time information with no payload fields.
18    DedicatedServerTickTime = 0,
19    /// Bee navigation and hive information.
20    Bee = 1,
21    /// Villager brain state.
22    VillagerBrain = 2,
23    /// Breeze combat and jump targets.
24    Breeze = 3,
25    /// One entity goal-selector entry.
26    GoalSelector = 4,
27    /// One entity path.
28    EntityPath = 5,
29    /// How an entity intersects its current block or fluid.
30    EntityBlockIntersection = 6,
31    /// Bee-hive state.
32    BeeHive = 7,
33    /// Point-of-interest state.
34    PointOfInterest = 8,
35    /// Redstone-wire orientation.
36    RedstoneWireOrientation = 9,
37    /// Village-section information with no payload fields.
38    VillageSection = 10,
39    /// Raid center positions.
40    Raid = 11,
41    /// Structure bounding boxes and pieces.
42    Structure = 12,
43    /// Game-event listener radius.
44    GameEventListener = 13,
45    /// Neighbor-update position.
46    NeighborUpdate = 14,
47    /// A game event and its world-space coordinates.
48    GameEvent = 15,
49}
50
51/// Bee navigation and hive debug data.
52#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
53#[type_struct_codec(kind = DebugSubscriptionData)]
54pub struct BeeDebugData {
55    /// Hive position, when one is assigned.
56    pub hive_position: PrefixedOptional<Position>,
57    /// Flower position, when one is remembered.
58    pub flower_position: PrefixedOptional<Position>,
59    /// Number of travel ticks.
60    pub travel_ticks: VarInt,
61    /// Hive positions this bee will not use.
62    pub blacklisted_hives: PrefixedArray<Position>,
63}
64
65/// Villager brain debug data.
66#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
67#[type_struct_codec(kind = DebugSubscriptionData)]
68pub struct VillagerBrainDebugData {
69    /// Villager name.
70    pub name: PrefixedString,
71    /// Villager profession name.
72    pub profession: PrefixedString,
73    /// Villager experience.
74    pub xp: Int,
75    /// Current health.
76    pub health: Float,
77    /// Maximum health.
78    pub max_health: Float,
79    /// Text representation of the inventory.
80    pub inventory: PrefixedString,
81    /// Whether the villager wants an iron golem.
82    pub wants_golem: Boolean,
83    /// Current anger level.
84    pub anger_level: Int,
85    /// Active brain activities.
86    pub activities: PrefixedArray<PrefixedString>,
87    /// Active behaviors.
88    pub behaviors: PrefixedArray<PrefixedString>,
89    /// Brain memories.
90    pub memories: PrefixedArray<PrefixedString>,
91    /// Villager gossips.
92    pub gossips: PrefixedArray<PrefixedString>,
93    /// Claimed points of interest.
94    pub pois: PrefixedArray<Position>,
95    /// Candidate points of interest.
96    pub potential_pois: PrefixedArray<Position>,
97}
98
99/// Breeze target debug data.
100#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
101#[type_struct_codec(kind = DebugSubscriptionData)]
102pub struct BreezeDebugData {
103    /// Optional target entity ID.
104    pub attack_target: PrefixedOptional<VarInt>,
105    /// Optional jump destination.
106    pub jump_target: PrefixedOptional<Position>,
107}
108
109/// A goal-selector name limited to 255 UTF-16 code units.
110#[repr(transparent)]
111#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
112pub struct DebugGoalName(String);
113
114impl DebugGoalName {
115    /// Maximum number of UTF-16 code units permitted by the protocol.
116    pub const MAX_UTF16_CODE_UNITS: usize = 255;
117    /// Maximum UTF-8 payload size permitted by the protocol.
118    pub const MAX_BYTES: usize = Self::MAX_UTF16_CODE_UNITS * 3;
119
120    /// Creates a validated goal-selector name.
121    pub fn new(value: impl Into<String>) -> Result<Self, DebugGoalNameTooLong> {
122        let value = value.into();
123        let actual_code_units = value.encode_utf16().count();
124        if actual_code_units > Self::MAX_UTF16_CODE_UNITS {
125            return Err(DebugGoalNameTooLong { actual_code_units });
126        }
127        Ok(Self(value))
128    }
129
130    /// Returns the name as a string slice.
131    #[must_use]
132    pub fn as_str(&self) -> &str {
133        &self.0
134    }
135
136    /// Extracts the owned string.
137    #[must_use]
138    pub fn into_inner(self) -> String {
139        self.0
140    }
141}
142
143impl AsRef<str> for DebugGoalName {
144    fn as_ref(&self) -> &str {
145        self.as_str()
146    }
147}
148
149impl fmt::Display for DebugGoalName {
150    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151        formatter.write_str(self.as_str())
152    }
153}
154
155impl TryFrom<String> for DebugGoalName {
156    type Error = DebugGoalNameTooLong;
157
158    fn try_from(value: String) -> Result<Self, Self::Error> {
159        Self::new(value)
160    }
161}
162
163impl TryFrom<&str> for DebugGoalName {
164    type Error = DebugGoalNameTooLong;
165
166    fn try_from(value: &str) -> Result<Self, Self::Error> {
167        Self::new(value)
168    }
169}
170
171impl From<DebugGoalName> for String {
172    fn from(value: DebugGoalName) -> Self {
173        value.into_inner()
174    }
175}
176
177impl TypeCodec for DebugGoalName {
178    fn encode(&self, writer: &mut impl std::io::Write) -> Result<(), CodecError> {
179        encode_prefixed_string(
180            &self.0,
181            writer,
182            CodecKind::String,
183            Self::MAX_BYTES,
184            Self::MAX_UTF16_CODE_UNITS,
185        )
186    }
187
188    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
189        decode_prefixed_string(
190            reader,
191            CodecKind::String,
192            Self::MAX_BYTES,
193            Self::MAX_UTF16_CODE_UNITS,
194        )
195        .map(|(value, _)| Self(value))
196    }
197}
198
199/// Error returned when a goal-selector name exceeds 255 UTF-16 code units.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
201pub struct DebugGoalNameTooLong {
202    /// Number of UTF-16 code units in the rejected name.
203    pub actual_code_units: usize,
204}
205
206impl fmt::Display for DebugGoalNameTooLong {
207    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
208        write!(
209            formatter,
210            "debug goal name contains {} UTF-16 code units; maximum is {}",
211            self.actual_code_units,
212            DebugGoalName::MAX_UTF16_CODE_UNITS
213        )
214    }
215}
216
217impl std::error::Error for DebugGoalNameTooLong {}
218
219/// One goal-selector debug entry.
220#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
221#[type_struct_codec(kind = DebugSubscriptionData)]
222pub struct GoalSelectorDebugData {
223    /// Goal priority.
224    pub priority: VarInt,
225    /// Whether the goal is currently running.
226    pub is_running: Boolean,
227    /// Goal name, limited to 255 UTF-16 code units.
228    pub name: DebugGoalName,
229}
230
231/// Path-node classification used by [`DebugPathNode`].
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ProtocolEnum)]
233#[protocol_enum(repr = VarInt)]
234pub enum DebugPathNodeType {
235    Blocked = 0,
236    Open = 1,
237    Walkable = 2,
238    WalkableDoor = 3,
239    Trapdoor = 4,
240    PowderSnow = 5,
241    DangerPowderSnow = 6,
242    Fence = 7,
243    Lava = 8,
244    Water = 9,
245    WaterBorder = 10,
246    Rail = 11,
247    UnpassableRail = 12,
248    DangerFire = 13,
249    DamageFire = 14,
250    DangerOther = 15,
251    DamageOther = 16,
252    DoorOpen = 17,
253    DoorWoodClosed = 18,
254    DoorIronClosed = 19,
255    Breach = 20,
256    Leaves = 21,
257    StickyHoney = 22,
258    Cocoa = 23,
259    DamageCautious = 24,
260    DangerTrapdoor = 25,
261}
262
263/// One node in an entity path debug payload.
264#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
265#[type_struct_codec(kind = DebugPathNode)]
266pub struct DebugPathNode {
267    pub x: Int,
268    pub y: Int,
269    pub z: Int,
270    pub walked_distance: Float,
271    pub cost_malus: Float,
272    pub closed: Boolean,
273    pub node_type: DebugPathNodeType,
274    /// The protocol field named `F`.
275    pub f: Float,
276}
277
278/// Entity pathfinding debug data.
279#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
280#[type_struct_codec(kind = DebugSubscriptionData)]
281pub struct EntityPathDebugData {
282    pub reached: Boolean,
283    pub next_block_index: Int,
284    pub block_position: Position,
285    pub nodes: PrefixedArray<DebugPathNode>,
286    pub target_nodes: PrefixedArray<DebugPathNode>,
287    pub open_set: PrefixedArray<DebugPathNode>,
288    pub closed_set: PrefixedArray<DebugPathNode>,
289    pub max_node_distance: Float,
290}
291
292/// Entity/block intersection state.
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ProtocolEnum)]
294#[protocol_enum(repr = VarInt)]
295pub enum EntityBlockIntersectionState {
296    InBlock = 0,
297    InFluid = 1,
298    InAir = 2,
299}
300
301/// Entity/block intersection debug data.
302#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
303#[type_struct_codec(kind = DebugSubscriptionData)]
304pub struct EntityBlockIntersectionDebugData {
305    pub state: EntityBlockIntersectionState,
306}
307
308/// Bee-hive debug data.
309#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
310#[type_struct_codec(kind = DebugSubscriptionData)]
311pub struct BeeHiveDebugData {
312    /// ID in the `minecraft:block` registry.
313    pub block_type: VarInt,
314    pub occupant_count: VarInt,
315    pub honey_level: VarInt,
316    pub sedated: Boolean,
317}
318
319/// Point-of-interest debug data.
320#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
321#[type_struct_codec(kind = DebugSubscriptionData)]
322pub struct PointOfInterestDebugData {
323    pub position: Position,
324    /// ID in the `minecraft:point_of_interest_type` registry.
325    pub poi_type: VarInt,
326    pub free_ticket_count: VarInt,
327}
328
329/// Redstone-wire orientation debug data.
330#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
331#[type_struct_codec(kind = DebugSubscriptionData)]
332pub struct RedstoneWireOrientationDebugData {
333    pub id: VarInt,
334}
335
336/// Raid debug data.
337#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
338#[type_struct_codec(kind = DebugSubscriptionData)]
339pub struct RaidDebugData {
340    pub positions: PrefixedArray<Position>,
341}
342
343/// One structure piece bounding box.
344#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
345#[type_struct_codec(kind = DebugStructureInfo)]
346pub struct DebugStructurePiece {
347    pub bounding_box_min: Position,
348    pub bounding_box_max: Position,
349    pub is_start: Boolean,
350}
351
352/// Structure bounding-box debug information.
353#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
354#[type_struct_codec(kind = DebugStructureInfo)]
355pub struct DebugStructureInfo {
356    pub bounding_box_min: Position,
357    pub bounding_box_max: Position,
358    pub pieces: PrefixedArray<DebugStructurePiece>,
359}
360
361/// Structure debug data.
362#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
363#[type_struct_codec(kind = DebugSubscriptionData)]
364pub struct StructureDebugData {
365    pub structures: PrefixedArray<DebugStructureInfo>,
366}
367
368/// Game-event listener debug data.
369#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
370#[type_struct_codec(kind = DebugSubscriptionData)]
371pub struct GameEventListenerDebugData {
372    pub listener_radius: VarInt,
373}
374
375/// Neighbor-update debug data.
376#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
377#[type_struct_codec(kind = DebugSubscriptionData)]
378pub struct NeighborUpdateDebugData {
379    pub position: Position,
380}
381
382/// Game-event debug data.
383#[derive(Debug, Clone, PartialEq, TypeStructCodec)]
384#[type_struct_codec(kind = DebugSubscriptionData)]
385pub struct GameEventDebugData {
386    /// ID in the `minecraft:game_event` registry.
387    pub event: VarInt,
388    pub x: Double,
389    pub y: Double,
390    pub z: Double,
391}
392
393/// A payload selected by [`DebugSubscriptionType`].
394///
395/// The enum variant determines the discriminator written by
396/// [`DebugSubscriptionEvent`](crate::DebugSubscriptionEvent) and prevents a
397/// payload from being paired with the wrong subscription type.
398#[derive(Debug, Clone, PartialEq)]
399pub enum DebugSubscriptionData {
400    DedicatedServerTickTime,
401    Bee(BeeDebugData),
402    VillagerBrain(VillagerBrainDebugData),
403    Breeze(BreezeDebugData),
404    GoalSelector(GoalSelectorDebugData),
405    EntityPath(EntityPathDebugData),
406    EntityBlockIntersection(EntityBlockIntersectionDebugData),
407    BeeHive(BeeHiveDebugData),
408    PointOfInterest(PointOfInterestDebugData),
409    RedstoneWireOrientation(RedstoneWireOrientationDebugData),
410    VillageSection,
411    Raid(RaidDebugData),
412    Structure(StructureDebugData),
413    GameEventListener(GameEventListenerDebugData),
414    NeighborUpdate(NeighborUpdateDebugData),
415    GameEvent(GameEventDebugData),
416}
417
418impl DebugSubscriptionData {
419    /// Returns the discriminator associated with this payload.
420    #[must_use]
421    pub const fn subscription_type(&self) -> DebugSubscriptionType {
422        match self {
423            Self::DedicatedServerTickTime => DebugSubscriptionType::DedicatedServerTickTime,
424            Self::Bee(_) => DebugSubscriptionType::Bee,
425            Self::VillagerBrain(_) => DebugSubscriptionType::VillagerBrain,
426            Self::Breeze(_) => DebugSubscriptionType::Breeze,
427            Self::GoalSelector(_) => DebugSubscriptionType::GoalSelector,
428            Self::EntityPath(_) => DebugSubscriptionType::EntityPath,
429            Self::EntityBlockIntersection(_) => DebugSubscriptionType::EntityBlockIntersection,
430            Self::BeeHive(_) => DebugSubscriptionType::BeeHive,
431            Self::PointOfInterest(_) => DebugSubscriptionType::PointOfInterest,
432            Self::RedstoneWireOrientation(_) => DebugSubscriptionType::RedstoneWireOrientation,
433            Self::VillageSection => DebugSubscriptionType::VillageSection,
434            Self::Raid(_) => DebugSubscriptionType::Raid,
435            Self::Structure(_) => DebugSubscriptionType::Structure,
436            Self::GameEventListener(_) => DebugSubscriptionType::GameEventListener,
437            Self::NeighborUpdate(_) => DebugSubscriptionType::NeighborUpdate,
438            Self::GameEvent(_) => DebugSubscriptionType::GameEvent,
439        }
440    }
441
442    pub(crate) fn encode_payload(
443        &self,
444        writer: &mut impl std::io::Write,
445    ) -> Result<(), CodecError> {
446        match self {
447            Self::DedicatedServerTickTime | Self::VillageSection => Ok(()),
448            Self::Bee(value) => value.encode(writer),
449            Self::VillagerBrain(value) => value.encode(writer),
450            Self::Breeze(value) => value.encode(writer),
451            Self::GoalSelector(value) => value.encode(writer),
452            Self::EntityPath(value) => value.encode(writer),
453            Self::EntityBlockIntersection(value) => value.encode(writer),
454            Self::BeeHive(value) => value.encode(writer),
455            Self::PointOfInterest(value) => value.encode(writer),
456            Self::RedstoneWireOrientation(value) => value.encode(writer),
457            Self::Raid(value) => value.encode(writer),
458            Self::Structure(value) => value.encode(writer),
459            Self::GameEventListener(value) => value.encode(writer),
460            Self::NeighborUpdate(value) => value.encode(writer),
461            Self::GameEvent(value) => value.encode(writer),
462        }
463    }
464
465    pub(crate) fn decode_payload(
466        subscription_type: DebugSubscriptionType,
467        reader: &mut impl Read,
468    ) -> Result<Self, CodecError> {
469        match subscription_type {
470            DebugSubscriptionType::DedicatedServerTickTime => Ok(Self::DedicatedServerTickTime),
471            DebugSubscriptionType::Bee => BeeDebugData::decode(reader).map(Self::Bee),
472            DebugSubscriptionType::VillagerBrain => {
473                VillagerBrainDebugData::decode(reader).map(Self::VillagerBrain)
474            }
475            DebugSubscriptionType::Breeze => BreezeDebugData::decode(reader).map(Self::Breeze),
476            DebugSubscriptionType::GoalSelector => {
477                GoalSelectorDebugData::decode(reader).map(Self::GoalSelector)
478            }
479            DebugSubscriptionType::EntityPath => {
480                EntityPathDebugData::decode(reader).map(Self::EntityPath)
481            }
482            DebugSubscriptionType::EntityBlockIntersection => {
483                EntityBlockIntersectionDebugData::decode(reader).map(Self::EntityBlockIntersection)
484            }
485            DebugSubscriptionType::BeeHive => BeeHiveDebugData::decode(reader).map(Self::BeeHive),
486            DebugSubscriptionType::PointOfInterest => {
487                PointOfInterestDebugData::decode(reader).map(Self::PointOfInterest)
488            }
489            DebugSubscriptionType::RedstoneWireOrientation => {
490                RedstoneWireOrientationDebugData::decode(reader).map(Self::RedstoneWireOrientation)
491            }
492            DebugSubscriptionType::VillageSection => Ok(Self::VillageSection),
493            DebugSubscriptionType::Raid => RaidDebugData::decode(reader).map(Self::Raid),
494            DebugSubscriptionType::Structure => {
495                StructureDebugData::decode(reader).map(Self::Structure)
496            }
497            DebugSubscriptionType::GameEventListener => {
498                GameEventListenerDebugData::decode(reader).map(Self::GameEventListener)
499            }
500            DebugSubscriptionType::NeighborUpdate => {
501                NeighborUpdateDebugData::decode(reader).map(Self::NeighborUpdate)
502            }
503            DebugSubscriptionType::GameEvent => {
504                GameEventDebugData::decode(reader).map(Self::GameEvent)
505            }
506        }
507    }
508}