kojacoord-protocol 0.1.0

Minecraft protocol types, packet (de)serialization and version registry for the Kojacoord proxy.
Documentation
use bytes::{Buf, BufMut, BytesMut};
use serde::{Deserialize, Serialize};

use crate::{
    codec::{Decode, Encode},
    error::ProtocolError,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Position {
    pub x: i32,

    pub y: i32,

    pub z: i32,
}

impl Position {
    pub fn new(x: i32, y: i32, z: i32) -> Self {
        Self { x, y, z }
    }
}

impl Encode for Position {
    fn encode(&self, dst: &mut BytesMut) -> Result<(), ProtocolError> {
        let x = (self.x as i64) & 0x3FFFFFF;
        let z = (self.z as i64) & 0x3FFFFFF;
        let y = (self.y as i64) & 0xFFF;

        let packed = ((x << 38) | (z << 12) | y) as u64;
        dst.put_u64(packed);
        Ok(())
    }
}

impl Decode for Position {
    fn decode(src: &mut bytes::Bytes) -> Result<Self, ProtocolError> {
        if src.remaining() < 8 {
            return Err(ProtocolError::UnexpectedEof);
        }

        let val = src.get_u64() as i64;

        let mut x = (val >> 38) & 0x3FFFFFF;
        let mut y = val & 0xFFF;
        let mut z = (val >> 12) & 0x3FFFFFF;

        if x >= 0x2000000 {
            x -= 0x4000000;
        }
        if z >= 0x2000000 {
            z -= 0x4000000;
        }

        if y >= 0x800 {
            y -= 0x1000;
        }

        Ok(Position {
            x: x as i32,
            y: y as i32,
            z: z as i32,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn roundtrip(pos: Position) -> Position {
        let mut buf = BytesMut::new();
        pos.encode(&mut buf).unwrap();
        let mut bytes = buf.freeze();
        Position::decode(&mut bytes).unwrap()
    }

    #[test]
    fn roundtrip_origin() {
        let p = Position::new(0, 0, 0);
        assert_eq!(roundtrip(p), p);
    }

    #[test]
    fn roundtrip_positive() {
        let p = Position::new(18357644, 831, -20882616);
        assert_eq!(roundtrip(p), p);
    }

    #[test]
    fn roundtrip_negative() {
        let p = Position::new(-1, -2048, -1);
        assert_eq!(roundtrip(p), p);
    }

    #[test]
    fn roundtrip_mixed() {
        let p = Position::new(100, 256, -100);
        assert_eq!(roundtrip(p), p);
    }

    #[test]
    fn roundtrip_max_values() {
        let p = Position::new(33554431, 2047, -33554432);
        assert_eq!(roundtrip(p), p);
    }
}