client = []
conn = ["dep:mt_rudp", "dep:thiserror"]
random = ["dep:generate-random", "dep:rand"]
-serde = ["dep:serde", "dep:serde_arrays", "enumset/serde"]
+serde = ["dep:serde", "dep:serde_arrays", "enumset/serde", "cgmath/serde", "collision/serde"]
server = []
test = ["client", "server", "random"]
[dependencies]
async-trait = "0.1.64"
+cgmath = "0.17.0"
+collision = "0.20.1"
delegate = "0.9.0"
enumset = { git = "https://github.com/Lymia/enumset" }
generate-random = { git = "https://github.com/minetest-rust/generate-random", features = ["enumset"], optional = true }
#[cfg(feature = "conn")]
pub use conn::*;
+pub use cgmath::{Deg, Euler, Point2, Point3, Rad, Vector2, Vector3};
+pub use collision::{Aabb2, Aabb3};
+
+pub const BS: f32 = 10.0;
+
mod to_clt;
mod to_srv;
username: String,
} = 2,
AcceptAuth {
- player_pos: [f32; 3],
+ #[mt(multiplier = "BS")]
+ #[mt(map_ser = "|x| Ok(x + Vector3::new(0.0, 0.5, 0.0) * BS)")]
+ #[mt(map_des = "|x: Point3<f32>| Ok(x - Vector3::new(0.0, 0.5, 0.0) * BS)")]
+ player_pos: Point3<f32>,
map_seed: u64,
send_interval: f32,
sudo_auth_methods: EnumSet<AuthMethod>,
DenySudoMode = 5,
Kick(KickReason) = 10,
BlockData {
- pos: [i16; 3],
+ pos: Point3<i16>,
#[mt(zstd)]
block: Box<MapBlock>,
} = 32,
AddNode {
- pos: [i16; 3],
+ pos: Point3<i16>,
param0: u16,
param1: u8,
param2: u8,
map_range: u32,
} = 42,
AddPlayerVelocity {
- vel: [f32; 3],
+ #[mt(multiplier = "BS")]
+ vel: Vector3<f32>,
} = 43,
MediaPush {
raw_hash: String,
damage_effect: bool,
} = 51,
MovePlayer {
- pos: [f32; 3],
- pitch: f32,
- yaw: f32,
+ #[mt(multiplier = "BS")]
+ pos: Point3<f32>,
+ pitch: Deg<f32>,
+ yaw: Deg<f32>,
} = 52,
LegacyKick {
#[mt(len = "Utf16")]
reason: String,
} = 53,
Fov {
- fov: f32,
+ fov: Deg<f32>,
multiplier: bool,
transition_time: f32,
} = 54,
DeathScreen {
point_cam: bool,
- point_at: [f32; 3],
+ #[mt(multiplier = "BS")]
+ point_at: Point3<f32>,
} = 55,
Media {
n: u16,
name: String,
gain: f32,
source: SoundSource,
- pos: [f32; 3],
+ #[mt(multiplier = "BS")]
+ pos: Point3<f32>,
src_obj_id: u16,
#[serde(rename = "loop")]
sound_loop: bool,
gravity: f32,
} = 69,
SpawnParticle {
- pos: [f32; 3],
- vel: [f32; 3],
- acc: [f32; 3],
+ pos: Point3<f32>,
+ vel: Vector3<f32>,
+ acc: Vector3<f32>,
expiration_time: f32,
size: f32,
collide: bool,
node_param2: u8,
node_tile: u8,
} = 70,
+ // TODO: support new particlespawner definitions (with tweening)
AddParticleSpawner {
amount: u16,
duration: f32,
- pos: RangeInclusive<[f32; 3]>,
- vel: RangeInclusive<[f32; 3]>,
- acc: RangeInclusive<[f32; 3]>,
+ pos: RangeInclusive<Point3<f32>>,
+ vel: RangeInclusive<Vector3<f32>>,
+ acc: RangeInclusive<Vector3<f32>>,
expiration_time: RangeInclusive<f32>,
size: RangeInclusive<f32>,
collide: bool,
ratio: u16,
} = 80,
LocalPlayerAnim {
- idle: [i32; 2],
- walk: [i32; 2],
- dig: [i32; 2],
- walk_dig: [i32; 2],
+ idle: RangeInclusive<i32>,
+ walk: RangeInclusive<i32>,
+ dig: RangeInclusive<i32>,
+ walk_dig: RangeInclusive<i32>,
speed: f32,
} = 81,
EyeOffset {
- first: [f32; 3],
- third: [f32; 3],
+ #[mt(multiplier = "BS")]
+ first: Vector3<f32>,
+ #[mt(multiplier = "BS")]
+ third: Vector3<f32>,
} = 82,
RemoveParticleSpawner {
id: u32,
NodeMetasChanged {
#[mt(size = "u32", zlib)]
#[mt(len = "NodeMetasLen")]
- changed: HashMap<[i16; 3], NodeMeta>,
+ changed: HashMap<Vector3<i16>, NodeMeta>,
} = 89,
SunParams(SunParams) = 90,
MoonParams(MoonParams) = 91,
#[mt_derive(to = "clt", repr = "u8", tag = "attribute", content = "value")]
pub enum HudChange {
- Pos([f32; 2]) = 0,
+ Pos(Point2<f32>) = 0,
Name(String),
- Scale([f32; 2]),
+ Scale(Vector2<f32>),
Text(String),
Number(u32),
Item(u32),
Dir(u32),
- Align([f32; 2]),
- Offset([f32; 2]),
- WorldPos([f32; 3]),
- Size([i32; 2]),
+ Align(Vector2<f32>),
+ Offset(Vector2<f32>),
+ WorldPos(Point3<f32>),
+ Size(Vector2<i32>),
ZIndex(i32), // this is i16 in HudAdd, minetest is weird
Text2(String),
Style(EnumSet<HudStyleFlag>),
#[mt_derive(to = "clt")]
pub struct HudElement {
pub hud_type: HudType,
- pub pos: [f32; 2],
+ pub pos: Point2<f32>,
pub name: String,
- pub scale: [f32; 2],
+ pub scale: Vector2<f32>,
pub text: String,
pub number: u32,
pub item: u32,
pub dir: u32,
- pub align: [f32; 2],
- pub offset: [f32; 2],
- pub world_pos: [f32; 3],
- pub size: [i32; 2],
+ pub align: Vector2<f32>,
+ pub offset: Vector2<f32>,
+ pub world_pos: Point3<f32>,
+ pub size: Vector2<i32>,
pub z_index: i16,
pub text_2: String,
pub style: EnumSet<HudStyleFlag>,
.try_collect::<Vec<_>>()?;
String::from_utf8(utf8)
- .map_err(|e| mt_ser::DeserializeError::Other(format!("Invalid UTF-8: {e}").into()))
+ .map_err(|e| mt_ser::DeserializeError::Other(format!("Invalid UTF-8: {e}")))
}
#[cfg(feature = "client")]
pub enum TileAnim {
None = 0,
VerticalFrame {
- n_frames: [u16; 2],
+ n_frames: Vector2<u16>,
duration: f32,
},
SpriteSheet {
- aspect_ratio: [u8; 2],
+ aspect_ratio: Vector2<u8>,
duration: f32,
},
}
}
}
+trait BsAabb: Sized {
+ fn ser(&self) -> Self;
+ fn des(&self) -> Self;
+}
+
+impl BsAabb for Aabb3<f32> {
+ fn ser(&self) -> Self {
+ collision::Aabb::mul_s(self, BS)
+ }
+
+ fn des(&self) -> Self {
+ collision::Aabb::mul_s(self, BS)
+ }
+}
+
+impl<T: BsAabb> BsAabb for Vec<T> {
+ fn ser(&self) -> Self {
+ self.iter().map(BsAabb::ser).collect()
+ }
+
+ fn des(&self) -> Self {
+ self.iter().map(BsAabb::des).collect()
+ }
+}
+
+impl<T: BsAabb, const N: usize> BsAabb for [T; N] {
+ fn ser(&self) -> Self {
+ std::array::from_fn(|i| self[i].ser())
+ }
+
+ fn des(&self) -> Self {
+ std::array::from_fn(|i| self[i].des())
+ }
+}
+
+#[cfg(feature = "server")]
+fn ser_bs_aabb<T: BsAabb>(aabb: &T) -> Result<T, mt_ser::SerializeError> {
+ Ok(aabb.ser())
+}
+
+#[cfg(feature = "client")]
+fn des_bs_aabb<T: BsAabb>(aabb: T) -> Result<T, mt_ser::DeserializeError> {
+ Ok(aabb.des())
+}
+
+#[mt_derive(to = "clt")]
+pub struct MountedNodeBox {
+ #[mt(map_ser = "ser_bs_aabb", map_des = "des_bs_aabb")]
+ wall_top: Aabb3<f32>,
+ #[mt(map_ser = "ser_bs_aabb", map_des = "des_bs_aabb")]
+ wall_bottom: Aabb3<f32>,
+ #[mt(map_ser = "ser_bs_aabb", map_des = "des_bs_aabb")]
+ wall_sides: Aabb3<f32>,
+}
+
+#[mt_derive(to = "clt")]
+pub struct ConnectedNodeBox {
+ #[mt(map_ser = "ser_bs_aabb", map_des = "des_bs_aabb")]
+ fixed: Vec<Aabb3<f32>>,
+ #[mt(map_ser = "ser_bs_aabb", map_des = "des_bs_aabb")]
+ connect_dirs: [Vec<Aabb3<f32>>; 6],
+ #[mt(map_ser = "ser_bs_aabb", map_des = "des_bs_aabb")]
+ disconnect_dirs: [Vec<Aabb3<f32>>; 6],
+ #[mt(map_ser = "ser_bs_aabb", map_des = "des_bs_aabb")]
+ disconnect_all: Vec<Aabb3<f32>>,
+ #[mt(map_ser = "ser_bs_aabb", map_des = "des_bs_aabb")]
+ disconnect_sides: Vec<Aabb3<f32>>,
+}
+
#[mt_derive(to = "clt", repr = "u8", tag = "type")]
#[mt(const_before = "6u8")]
pub enum NodeBox {
Cube = 0,
Fixed {
- fixed: Vec<RangeInclusive<[f32; 3]>>,
- },
- Mounted {
- wall_top: RangeInclusive<[f32; 3]>,
- wall_bottom: RangeInclusive<[f32; 3]>,
- wall_sides: RangeInclusive<[f32; 3]>,
+ #[mt(map_ser = "ser_bs_aabb", map_des = "des_bs_aabb")]
+ fixed: Vec<Aabb3<f32>>,
},
+ Mounted(Box<MountedNodeBox>),
Leveled {
- fixed: Vec<RangeInclusive<[f32; 3]>>,
- },
- Connected {
- fixed: Vec<RangeInclusive<[f32; 3]>>,
- connect_dirs: [Vec<RangeInclusive<[f32; 3]>>; 6],
- disconnect_dirs: [Vec<RangeInclusive<[f32; 3]>>; 6],
- disconnect_all: Vec<RangeInclusive<[f32; 3]>>,
- disconnect_sides: Vec<RangeInclusive<[f32; 3]>>,
+ #[mt(map_ser = "ser_bs_aabb", map_des = "des_bs_aabb")]
+ fixed: Vec<Aabb3<f32>>,
},
+ Connected(Box<ConnectedNodeBox>),
}
#[mt_derive(to = "clt")]
pub description: String,
pub inventory_image: String,
pub wield_image: String,
- pub wield_scale: [f32; 3],
+ pub wield_scale: Vector3<f32>,
pub stack_max: u16,
pub usable: bool,
pub can_point_liquids: bool,
pub max_hp: u16, // player only
pub collide_with_nodes: bool,
pub weight: f32, // deprecated
- pub collision_box: RangeInclusive<[f32; 3]>,
- pub selection_box: RangeInclusive<[f32; 3]>,
+ pub collision_box: Aabb3<f32>,
+ pub selection_box: Aabb3<f32>,
pub pointable: bool,
pub visual: ObjVisual,
- pub visual_size: [f32; 3],
+ pub visual_size: Vector3<f32>,
pub textures: Vec<String>,
- pub sprite_sheet_size: [i16; 2], // in sprites
- pub sprite_pos: [i16; 2], // in sprite sheet
+ pub sprite_sheet_size: Vector2<i16>, // in sprites
+ pub sprite_pos: Point2<i16>, // in sprite sheet
pub visible: bool,
pub make_footstep_sounds: bool,
- pub rotate_speed: f32, // in radians per second
+ pub rotate_speed: Rad<f32>, // per second
pub mesh: String,
pub colors: Vec<Color>,
pub collide_with_objs: bool,
pub step_height: f32,
pub face_rotate_dir: bool,
- pub face_rotate_dir_off: f32, // in degrees
+ pub face_rotate_dir_off: Deg<f32>,
pub backface_cull: bool,
pub nametag: String,
pub nametag_color: Color,
- pub face_rotate_speed: f32, // in degrees per second
+ pub face_rotate_speed: Deg<f32>, // per second
pub infotext: String,
pub itemstring: String,
pub glow: i8,
- pub max_breath: u16, // player only
- pub eye_height: f32, // player only
- pub zoom_fov: f32, // in degrees. player only
+ pub max_breath: u16, // player only
+ pub eye_height: f32, // player only
+ pub zoom_fov: Deg<f32>, // player only
pub use_texture_alpha: bool,
pub dmg_texture_mod: String, // suffix
pub shaded: bool,
#[mt_derive(to = "clt")]
pub struct ObjPos {
- pub pos: [f32; 3],
- pub vel: [f32; 3],
- pub acc: [f32; 3],
- pub rot: [f32; 3],
+ #[mt(multiplier = "BS")]
+ pub pos: Point3<f32>,
+ #[mt(multiplier = "BS")]
+ pub vel: Vector3<f32>,
+ #[mt(multiplier = "BS")]
+ pub acc: Vector3<f32>,
+ pub rot: Euler<Deg<f32>>,
pub interpolate: bool,
pub end: bool,
pub update_interval: f32,
#[mt_derive(to = "clt")]
pub struct ObjSprite {
- pub frame0: [i16; 2],
+ pub frame_0: Point2<i16>,
pub frames: u16,
pub frame_duration: f32,
pub view_angle_frames: bool,
#[mt_derive(to = "clt")]
pub struct ObjAnim {
- pub frames: [i32; 2],
+ pub frames: Vector2<i32>,
pub speed: f32,
pub blend: f32,
pub no_loop: bool,
#[mt_derive(to = "clt")]
pub struct ObjBonePos {
- pub pos: [f32; 3],
- pub rot: [f32; 3],
+ pub pos: Point3<f32>,
+ pub rot: Euler<Deg<f32>>,
}
#[mt_derive(to = "clt")]
pub struct ObjAttach {
pub parent_id: u16,
pub bone: String,
- pub pos: [f32; 3],
- pub rot: [f32; 3],
+ pub pos: Point3<f32>,
+ pub rot: Euler<Deg<f32>>,
pub force_visible: bool,
}
pub name: String,
pub is_player: bool,
pub id: u16,
- pub pos: [f32; 3],
- pub rot: [f32; 3],
+ #[mt(multiplier = "BS")]
+ pub pos: Point3<f32>,
+ pub rot: Euler<Deg<f32>>,
pub hp: u16,
#[mt(len = "u8")]
pub msgs: Vec<ObjInitMsg>,
pub ambient_color: Color,
pub height: f32,
pub thickness: f32,
- pub speed: [f32; 2],
+ pub speed: Vector2<f32>,
}
Zoom,
}
+#[cfg(feature = "client")]
+fn ser_cast_err() -> mt_ser::SerializeError {
+ mt_ser::SerializeError::Other("cast failed".into())
+}
+
+#[cfg(feature = "server")]
+fn des_cast_err() -> mt_ser::DeserializeError {
+ mt_ser::DeserializeError::Other("cast failed".into())
+}
+
#[mt_derive(to = "srv")]
pub struct PlayerPos {
- pub pos_100: [i32; 3],
- pub vel_100: [i32; 3],
- pub pitch_100: i32,
- pub yaw_100: i32,
+ #[mt(multiplier = "100.0 * BS")]
+ #[mt(map_ser = "|x| x.cast::<i32>().ok_or_else(ser_cast_err)")]
+ #[mt(map_des = "|x: Point3<i32>| x.cast::<f32>().ok_or_else(des_cast_err)")]
+ pub pos: Point3<f32>,
+ #[mt(multiplier = "100.0 * BS")]
+ #[mt(map_ser = "|x| x.cast::<i32>().ok_or_else(ser_cast_err)")]
+ #[mt(map_des = "|x: Vector3<i32>| x.cast::<f32>().ok_or_else(des_cast_err)")]
+ pub vel: Vector3<f32>,
+ #[mt(map_ser = "|x| Ok(x.0 as i32)", map_des = "|x: i32| Ok(Deg(x as f32))")]
+ pub pitch: Deg<f32>,
+ #[mt(map_ser = "|x| Ok(x.0 as i32)", map_des = "|x: i32| Ok(Deg(x as f32))")]
+ pub yaw: Deg<f32>,
pub keys: EnumSet<Key>,
- pub fov_80: u8,
+ #[mt(multiplier = "80.0")]
+ #[mt(map_ser = "|x| Ok(x.0 as u8)", map_des = "|x: u8| Ok(Rad(x as f32))")]
+ pub fov: Rad<f32>,
pub wanted_range: u8,
}
#[mt(const_before = "0u8")] // version
pub enum PointedThing {
None = 0,
- Node { under: [i16; 3], above: [i16; 3] },
- Obj { obj: u16 },
+ Node {
+ under: Point3<i16>,
+ above: Point3<i16>,
+ },
+ Obj {
+ obj: u16,
+ },
}
#[mt_derive(to = "srv", repr = "u16", tag = "type", content = "data")]
Init {
serialize_version: u8,
#[mt(const_before = "1u16")] // supported compression
- min_proto_version: u16,
- max_proto_version: u16,
+ proto_version: RangeInclusive<u16>,
player_name: String,
#[mt(default)]
send_full_item_meta: bool,
PlayerPos(PlayerPos) = 35,
GotBlocks {
#[mt(len = "u8")]
- blocks: Vec<[i16; 3]>,
+ blocks: Vec<Point3<i16>>,
} = 36,
DeletedBlocks {
#[mt(len = "u8")]