Skip to main content

yog_body/
wire.rs

1//! What the host and a mod say each frame about one entity (see
2//! `docs/design/yog-body.md`): the [`Frame`] the host sends, the [`Ops`]
3//! the mod answers with. Little-endian; strings are a u16 length and UTF-8.
4
5use crate::math::{Mat4, Vec3};
6use crate::model::Quad;
7
8/// One entity, this frame.
9#[derive(Debug, Clone, Default, PartialEq)]
10pub struct Frame {
11    pub uuid: String,
12    /// `minecraft:player`, `minecraft:zombie`…
13    pub entity_type: String,
14    /// Between the last tick and the next (0–1).
15    pub partial: f32,
16    /// Ticks it has lived, with `partial`.
17    pub age: f32,
18    /// How far its legs have walked, and how fast (vanilla's limb swing).
19    pub limb_swing: f32,
20    pub limb_amount: f32,
21    /// The head's turn from the body, and its pitch (degrees).
22    pub head_yaw: f32,
23    pub head_pitch: f32,
24    /// Degrees.
25    pub body_yaw: f32,
26    pub pitch: f32,
27    pub pos: [f64; 3],
28    /// Blocks per tick.
29    pub velocity: Vec3,
30    /// `yog_core::query::entity_flags` bits.
31    pub flags: u32,
32    /// The world's time in ticks, with `partial` — the same on every
33    /// client, so clips timed by it play alike for everyone.
34    pub game_time: f64,
35    /// What was captured last frame (see [`Ops::capture`]).
36    pub captures: Vec<Capture>,
37    /// What the entity keeps by key (`Server::set_entity_data`), as this
38    /// client was told.
39    pub data: Vec<(String, Vec<u8>)>,
40}
41
42/// The posed geometry of an entity (entity space, see the design doc).
43#[derive(Debug, Clone, Default, PartialEq)]
44pub struct Capture {
45    /// `self`, or the entity type captured.
46    pub source: String,
47    pub meshes: Vec<Mesh>,
48}
49
50/// Quads with one texture.
51#[derive(Debug, Clone, Default, PartialEq)]
52pub struct Mesh {
53    pub texture: String,
54    pub quads: Vec<Quad>,
55}
56
57/// Where a mesh's vertices are.
58#[derive(Debug, Clone, PartialEq)]
59pub enum Anchor {
60    /// Blocks, y up, feet at the origin, after the body's yaw.
61    Entity,
62    /// The vanilla model's space: pixels, y down.
63    Model,
64    /// A vanilla part's space (pixels), wherever the part is posed.
65    Bone(String),
66    /// A block's space: pixels 0–16, y up, as block models (block bodies).
67    Block,
68}
69
70/// How a mesh is drawn.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
72pub enum Mode {
73    /// Opaque, transparent pixels cut out, both sides.
74    #[default]
75    Cutout,
76    /// Blended by its alpha.
77    Translucent,
78    /// Glows: full bright, added.
79    Emissive,
80}
81
82/// What to change about a vanilla part.
83#[derive(Debug, Clone, Copy, PartialEq, Default)]
84pub struct BoneOp {
85    /// Radians; replaces vanilla's turn if `set_rotation`, else adds.
86    pub rotation: Vec3,
87    pub set_rotation: bool,
88    /// Pixels; replaces vanilla's offset if `set_position`, else adds.
89    pub position: Vec3,
90    pub set_position: bool,
91    pub scale: Option<Vec3>,
92    /// `Some(false)` hides it, `Some(true)` shows it.
93    pub visible: Option<bool>,
94}
95
96/// A mod's answer: operations, written in order.
97#[derive(Debug, Clone, Default, PartialEq)]
98pub struct Ops(pub Vec<u8>);
99
100const ROOT: u8 = 1;
101const BONE: u8 = 2;
102const MESH: u8 = 3;
103const DISGUISE: u8 = 4;
104const HIDE: u8 = 5;
105const CAPTURE: u8 = 6;
106
107impl Ops {
108    pub fn is_empty(&self) -> bool {
109        self.0.is_empty()
110    }
111
112    /// Turns the whole body by `m` in entity space (after the body's yaw).
113    pub fn root(&mut self, m: &Mat4) {
114        self.0.push(ROOT);
115        for f in m.0 {
116            put_f32(&mut self.0, f);
117        }
118    }
119
120    /// Poses the vanilla part `bone` (after vanilla animated it).
121    pub fn bone(&mut self, bone: &str, op: &BoneOp) {
122        let b = &mut self.0;
123        b.push(BONE);
124        put_str(b, bone);
125        let mut flags = 0u8;
126        if op.set_rotation {
127            flags |= 1;
128        }
129        if op.set_position {
130            flags |= 2;
131        }
132        if op.scale.is_some() {
133            flags |= 4;
134        }
135        match op.visible {
136            Some(false) => flags |= 8,
137            Some(true) => flags |= 16,
138            None => {}
139        }
140        b.push(flags);
141        for v in [op.rotation, op.position, op.scale.unwrap_or(Vec3::ONE)] {
142            put_vec(b, v);
143        }
144    }
145
146    /// Draws `quads` with `texture` at `anchor`. `argb` tints it (alpha
147    /// counts in [`Mode::Translucent`]); `glint` adds the enchantment shine.
148    pub fn mesh(&mut self, anchor: &Anchor, texture: &str, mode: Mode, glint: bool, argb: u32, quads: &[Quad]) {
149        if quads.is_empty() {
150            return;
151        }
152        let b = &mut self.0;
153        b.push(MESH);
154        let (a, bone) = match anchor {
155            Anchor::Entity => (0u8, ""),
156            Anchor::Model => (1, ""),
157            Anchor::Bone(n) => (2, n.as_str()),
158            Anchor::Block => (3, ""),
159        };
160        b.push(a);
161        put_str(b, bone);
162        put_str(b, texture);
163        b.push(match mode {
164            Mode::Cutout => 0,
165            Mode::Translucent => 1,
166            Mode::Emissive => 2,
167        });
168        b.push(glint as u8);
169        b.extend_from_slice(&argb.to_le_bytes());
170        put_quads(b, quads);
171    }
172
173    /// Draws the entity as one of `entity_type`, animated like it.
174    pub fn disguise(&mut self, entity_type: &str) {
175        self.0.push(DISGUISE);
176        put_str(&mut self.0, entity_type);
177    }
178
179    /// Doesn't draw the entity itself (its model, layers and name).
180    pub fn hide(&mut self) {
181        self.0.push(HIDE);
182    }
183
184    /// Asks for the posed geometry of the entity (`"self"`) or of a type
185    /// posed like it; it comes in the next frame's [`Frame::captures`].
186    pub fn capture(&mut self, source: &str) {
187        self.0.push(CAPTURE);
188        put_str(&mut self.0, source);
189    }
190}
191
192// ── Encoding ────────────────────────────────────────────────────────────────
193
194fn put_f32(b: &mut Vec<u8>, f: f32) {
195    b.extend_from_slice(&f.to_le_bytes());
196}
197
198fn put_vec(b: &mut Vec<u8>, v: Vec3) {
199    put_f32(b, v.x);
200    put_f32(b, v.y);
201    put_f32(b, v.z);
202}
203
204fn put_str(b: &mut Vec<u8>, s: &str) {
205    let s = &s.as_bytes()[..s.len().min(u16::MAX as usize)];
206    b.extend_from_slice(&(s.len() as u16).to_le_bytes());
207    b.extend_from_slice(s);
208}
209
210/// Vertex count, then x y z u v nx ny nz per vertex.
211fn put_quads(b: &mut Vec<u8>, quads: &[Quad]) {
212    b.extend_from_slice(&((quads.len() * 4) as u32).to_le_bytes());
213    for q in quads {
214        for i in 0..4 {
215            put_vec(b, q.pos[i]);
216            put_f32(b, q.uv[i][0]);
217            put_f32(b, q.uv[i][1]);
218            put_vec(b, q.normal);
219        }
220    }
221}
222
223struct Reader<'a> {
224    b: &'a [u8],
225}
226
227impl Reader<'_> {
228    fn take(&mut self, n: usize) -> Option<&[u8]> {
229        if self.b.len() < n {
230            return None;
231        }
232        let (h, t) = self.b.split_at(n);
233        self.b = t;
234        Some(h)
235    }
236    fn u8(&mut self) -> Option<u8> {
237        self.take(1).map(|b| b[0])
238    }
239    fn u16(&mut self) -> Option<u16> {
240        self.take(2).map(|b| u16::from_le_bytes([b[0], b[1]]))
241    }
242    fn u32(&mut self) -> Option<u32> {
243        self.take(4).map(|b| u32::from_le_bytes(b.try_into().unwrap()))
244    }
245    fn f32(&mut self) -> Option<f32> {
246        self.take(4).map(|b| f32::from_le_bytes(b.try_into().unwrap()))
247    }
248    fn f64(&mut self) -> Option<f64> {
249        self.take(8).map(|b| f64::from_le_bytes(b.try_into().unwrap()))
250    }
251    fn vec(&mut self) -> Option<Vec3> {
252        Some(Vec3::new(self.f32()?, self.f32()?, self.f32()?))
253    }
254    fn str(&mut self) -> Option<String> {
255        let n = self.u16()? as usize;
256        String::from_utf8(self.take(n)?.to_vec()).ok()
257    }
258    fn quads(&mut self) -> Option<Vec<Quad>> {
259        let n = self.u32()? as usize / 4;
260        let mut out = Vec::with_capacity(n.min(1 << 16));
261        for _ in 0..n {
262            let mut q = Quad { pos: [Vec3::ZERO; 4], uv: [[0.0; 2]; 4], normal: Vec3::ZERO };
263            for i in 0..4 {
264                q.pos[i] = self.vec()?;
265                q.uv[i] = [self.f32()?, self.f32()?];
266                q.normal = self.vec()?;
267            }
268            out.push(q);
269        }
270        Some(out)
271    }
272}
273
274impl Frame {
275    /// Reads what the host sent.
276    pub fn decode(bytes: &[u8]) -> Option<Frame> {
277        let mut r = Reader { b: bytes };
278        if r.u8()? != 2 {
279            return None;
280        }
281        let mut f = Frame {
282            uuid: r.str()?,
283            entity_type: r.str()?,
284            partial: r.f32()?,
285            age: r.f32()?,
286            limb_swing: r.f32()?,
287            limb_amount: r.f32()?,
288            head_yaw: r.f32()?,
289            head_pitch: r.f32()?,
290            body_yaw: r.f32()?,
291            pitch: r.f32()?,
292            pos: [r.f64()?, r.f64()?, r.f64()?],
293            velocity: r.vec()?,
294            flags: r.u32()?,
295            game_time: r.f64()?,
296            captures: Vec::new(),
297            data: Vec::new(),
298        };
299        for _ in 0..r.u16()? {
300            let source = r.str()?;
301            let mut meshes = Vec::new();
302            for _ in 0..r.u16()? {
303                meshes.push(Mesh { texture: r.str()?, quads: r.quads()? });
304            }
305            f.captures.push(Capture { source, meshes });
306        }
307        for _ in 0..r.u16()? {
308            let key = r.str()?;
309            let n = r.u32()? as usize;
310            f.data.push((key, r.take(n)?.to_vec()));
311        }
312        Some(f)
313    }
314
315    /// As the host writes it (for tests and tools).
316    pub fn encode(&self) -> Vec<u8> {
317        let mut b = vec![2u8];
318        put_str(&mut b, &self.uuid);
319        put_str(&mut b, &self.entity_type);
320        for f in [self.partial, self.age, self.limb_swing, self.limb_amount, self.head_yaw, self.head_pitch, self.body_yaw, self.pitch] {
321            put_f32(&mut b, f);
322        }
323        for p in self.pos {
324            b.extend_from_slice(&p.to_le_bytes());
325        }
326        put_vec(&mut b, self.velocity);
327        b.extend_from_slice(&self.flags.to_le_bytes());
328        b.extend_from_slice(&self.game_time.to_le_bytes());
329        b.extend_from_slice(&(self.captures.len() as u16).to_le_bytes());
330        for c in &self.captures {
331            put_str(&mut b, &c.source);
332            b.extend_from_slice(&(c.meshes.len() as u16).to_le_bytes());
333            for m in &c.meshes {
334                put_str(&mut b, &m.texture);
335                put_quads(&mut b, &m.quads);
336            }
337        }
338        b.extend_from_slice(&(self.data.len() as u16).to_le_bytes());
339        for (k, v) in &self.data {
340            put_str(&mut b, k);
341            b.extend_from_slice(&(v.len() as u32).to_le_bytes());
342            b.extend_from_slice(v);
343        }
344        b
345    }
346
347    pub fn has(&self, flag: u32) -> bool {
348        self.flags & flag != 0
349    }
350
351    /// What was captured from `source` last frame.
352    pub fn capture(&self, source: &str) -> Option<&Capture> {
353        self.captures.iter().find(|c| c.source == source)
354    }
355
356    /// The entity's age in seconds (differs between clients: each counts
357    /// from when it saw the entity).
358    pub fn seconds(&self) -> f32 {
359        self.age / 20.0
360    }
361
362    /// The world's time in seconds — for clips everyone sees alike.
363    pub fn world_seconds(&self) -> f32 {
364        (self.game_time / 20.0) as f32
365    }
366
367    /// The value the entity keeps under `key`.
368    pub fn data(&self, key: &str) -> Option<&[u8]> {
369        self.data.iter().find(|(k, _)| k == key).map(|(_, v)| v.as_slice())
370    }
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    #[test]
378    fn a_frame_reads_back() {
379        let q = Quad { pos: [Vec3::new(1.0, 2.0, 3.0); 4], uv: [[0.5, 0.25]; 4], normal: Vec3::new(0.0, 1.0, 0.0) };
380        let f = Frame {
381            uuid: "u".into(),
382            entity_type: "minecraft:player".into(),
383            age: 12.5,
384            pos: [1.0, 2.0, 3.0],
385            flags: 5,
386            game_time: 1234.5,
387            data: vec![("m:k".into(), vec![1, 2])],
388            captures: vec![Capture { source: "self".into(), meshes: vec![Mesh { texture: "t".into(), quads: vec![q] }] }],
389            ..Default::default()
390        };
391        assert_eq!(Frame::decode(&f.encode()), Some(f));
392    }
393
394    #[test]
395    fn ops_are_records() {
396        let mut o = Ops::default();
397        o.hide();
398        o.disguise("minecraft:zombie");
399        assert_eq!(o.0[0], HIDE);
400        assert_eq!(o.0[1], DISGUISE);
401        assert_eq!(&o.0[2..4], &16u16.to_le_bytes());
402    }
403}