]> git.lizzy.rs Git - mt_ser.git/commitdiff
Initial commit
authorLizzy Fleckenstein <eliasfleckenstein@web.de>
Mon, 6 Feb 2023 18:56:53 +0000 (19:56 +0100)
committerLizzy Fleckenstein <eliasfleckenstein@web.de>
Mon, 6 Feb 2023 18:56:53 +0000 (19:56 +0100)
13 files changed:
.gitignore [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
derive/Cargo.lock [new file with mode: 0644]
derive/Cargo.toml [new file with mode: 0644]
derive/src/lib.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/to_clt.rs [new file with mode: 0644]
src/to_clt/chat.rs [new file with mode: 0644]
src/to_clt/env.rs [new file with mode: 0644]
src/to_clt/hud.rs [new file with mode: 0644]
src/to_clt/media.rs [new file with mode: 0644]
src/to_clt/status.rs [new file with mode: 0644]
src/to_srv.rs [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..4fffb2f
--- /dev/null
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..b2f6f64
--- /dev/null
@@ -0,0 +1,19 @@
+[package]
+name = "mt_data"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+client = []
+random = ["dep:generate-random", "dep:rand"]
+serde = ["dep:serde", "dep:serde_arrays", "enumset/serde"]
+server = []
+
+[dependencies]
+enumset = { git = "https://github.com/Lymia/enumset" }
+generate-random = { git = "https://github.com/minetest-rust/generate-random", features = ["enumset"], optional = true }
+mt_data_derive = { path = "derive" }
+rand = { version = "0.8.5", optional = true }
+serde = { version = "1.0.152", features = ["derive"], optional = true }
+serde_arrays = { version = "0.1.0", optional = true }
+thiserror = "1.0.38"
diff --git a/derive/Cargo.lock b/derive/Cargo.lock
new file mode 100644 (file)
index 0000000..63bbb8c
--- /dev/null
@@ -0,0 +1,101 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "darling"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "mt_data_derive"
+version = "0.1.0"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
diff --git a/derive/Cargo.toml b/derive/Cargo.toml
new file mode 100644 (file)
index 0000000..5597554
--- /dev/null
@@ -0,0 +1,13 @@
+[package]
+name = "mt_data_derive"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = { version = "1", features = ["full", "extra-traits", "printing"] }
+quote = "1"
+proc-macro2 = "1"
+darling = "0.14.2"
diff --git a/derive/src/lib.rs b/derive/src/lib.rs
new file mode 100644 (file)
index 0000000..8daabf0
--- /dev/null
@@ -0,0 +1,170 @@
+use darling::FromMeta;
+use proc_macro::{self, TokenStream};
+use quote::{quote, ToTokens};
+use syn::{parse_macro_input, parse_quote};
+
+#[derive(Debug, FromMeta, Copy, Clone, Eq, PartialEq)]
+#[darling(rename_all = "snake_case")]
+enum To {
+       Clt,
+       Srv,
+}
+
+#[derive(Debug, FromMeta)]
+struct MacroArgs {
+       to: To,
+       repr: Option<syn::Type>,
+       tag: Option<String>,
+       content: Option<String>,
+       #[darling(default)]
+       enumset: bool,
+}
+
+fn wrap_attr(attr: &mut syn::Attribute) {
+       match attr.path.get_ident().map(|i| i.to_string()).as_deref() {
+               Some("mt") => {
+                       let path = attr.path.clone();
+                       let tokens = attr.tokens.clone();
+
+                       *attr = parse_quote! {
+                               #[cfg_attr(any(feature = "client", feature = "server"), #path #tokens)]
+                       };
+               }
+               Some("serde") => {
+                       let path = attr.path.clone();
+                       let tokens = attr.tokens.clone();
+
+                       *attr = parse_quote! {
+                               #[cfg_attr(feature = "serde", #path #tokens)]
+                       };
+               }
+               _ => {}
+       }
+}
+
+#[proc_macro_attribute]
+pub fn mt_derive(attr: TokenStream, item: TokenStream) -> TokenStream {
+       let item2 = item.clone();
+
+       let attr_args = parse_macro_input!(attr as syn::AttributeArgs);
+       let mut input = parse_macro_input!(item2 as syn::Item);
+
+       let args = match MacroArgs::from_list(&attr_args) {
+               Ok(v) => v,
+               Err(e) => {
+                       return TokenStream::from(e.write_errors());
+               }
+       };
+
+       let (serializer, deserializer) = match args.to {
+               To::Clt => ("server", "client"),
+               To::Srv => ("client", "server"),
+       };
+
+       let mut out = quote! {
+               #[derive(Debug)]
+               #[cfg_attr(feature = "random", derive(GenerateRandom))]
+               #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+       };
+
+       macro_rules! iter {
+               ($t:expr, $f:expr) => {
+                       $t.iter_mut().for_each($f)
+               };
+       }
+
+       match &mut input {
+               syn::Item::Enum(e) => {
+                       iter!(e.attrs, wrap_attr);
+                       iter!(e.variants, |v| {
+                               iter!(v.attrs, wrap_attr);
+                               iter!(v.fields, |f| iter!(f.attrs, wrap_attr));
+                       });
+
+                       let repr = args.repr.expect("missing repr for enum");
+
+                       if args.enumset {
+                               let repr_str = repr.to_token_stream().to_string();
+
+                               out.extend(quote! {
+                                       #[derive(EnumSetType)]
+                                       #[enumset(repr = #repr_str, serialize_as_map)]
+                               })
+                       } else {
+                               let has_payload = e
+                                       .variants
+                                       .iter()
+                                       .find_map(|v| if v.fields.is_empty() { None } else { Some(()) })
+                                       .is_some();
+
+                               if has_payload {
+                                       let tag = args.tag.expect("missing tag for enum with payload");
+
+                                       out.extend(quote! {
+                                               #[derive(Clone)]
+                                               #[cfg_attr(feature = "serde", serde(tag = #tag))]
+                                       });
+
+                                       if let Some(content) = args.content {
+                                               out.extend(quote! {
+                                                       #[cfg_attr(feature = "serde", serde(content = #content))]
+                                               });
+                                       }
+                               } else {
+                                       out.extend(quote! {
+                                               #[derive(Copy, Clone, PartialEq, Eq)]
+                                       });
+                               }
+
+                               out.extend(quote! {
+                                       #[repr(#repr)]
+                                       #[cfg_attr(feature = #serializer, derive(MtSerialize))]
+                                       #[cfg_attr(feature = #deserializer, derive(MtDeserialize))]
+                               });
+                       }
+
+                       out.extend(quote! {
+                               #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
+                       });
+               }
+               syn::Item::Struct(s) => {
+                       iter!(s.attrs, wrap_attr);
+                       iter!(s.fields, |f| iter!(f.attrs, wrap_attr));
+
+                       out.extend(quote! {
+                               #[derive(Clone)]
+                               #[cfg_attr(feature = #serializer, derive(MtSerialize))]
+                               #[cfg_attr(feature = #deserializer, derive(MtDeserialize))]
+                       });
+               }
+               _ => panic!("only enum and struct supported"),
+       }
+
+       out.extend(input.to_token_stream());
+       out.into()
+}
+
+#[proc_macro_derive(MtSerialize, attributes(mt))]
+pub fn derive_serialize(input: TokenStream) -> TokenStream {
+       let syn::DeriveInput { ident, .. } = parse_macro_input!(input);
+       let output = quote! {
+               impl MtSerialize for #ident {
+                       fn mt_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<(), mt_data::SerializeError> {
+                               Err(mt_data::SerializeError::Unimplemented)
+                       }
+               }
+       };
+       output.into()
+}
+
+#[proc_macro_derive(MtDeserialize, attributes(mt))]
+pub fn derive_deserialize(input: TokenStream) -> TokenStream {
+       let syn::DeriveInput { ident, .. } = parse_macro_input!(input);
+       quote! {
+               impl MtDeserialize for #ident {
+                       fn mt_deserialize<R: std::io::Read>(reader: &mut R) -> Result<Self, mt_data::DeserializeError> {
+                               Err(mt_data::DeserializeError::Unimplemented)
+                       }
+               }
+       }.into()
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..143b397
--- /dev/null
@@ -0,0 +1,52 @@
+pub use enumset;
+
+#[cfg(feature = "random")]
+pub use generate_random;
+
+#[cfg(feature = "random")]
+pub use rand;
+
+#[cfg(feature = "serde")]
+pub use serde;
+
+use enumset::{EnumSet, EnumSetType};
+use mt_data_derive::mt_derive;
+pub use mt_data_derive::{MtDeserialize, MtSerialize};
+use std::{collections::HashMap, fmt, io};
+use thiserror::Error;
+
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
+#[cfg(feature = "random")]
+use generate_random::GenerateRandom;
+
+#[derive(Error, Debug)]
+pub enum SerializeError {
+    #[error("{0}")]
+    IoError(#[from] io::Error),
+    #[error("serialization is not implemented")]
+    Unimplemented,
+}
+
+#[derive(Error, Debug)]
+pub enum DeserializeError {
+    #[error("{0}")]
+    IoError(#[from] io::Error),
+    #[error("deserialization is not implemented")]
+    Unimplemented,
+}
+
+pub trait MtSerialize: Sized {
+    fn mt_serialize<W: io::Write>(&self, writer: &mut W) -> Result<(), SerializeError>;
+}
+
+pub trait MtDeserialize: Sized {
+    fn mt_deserialize<R: io::Read>(reader: &mut R) -> Result<Self, DeserializeError>;
+}
+
+mod to_clt;
+mod to_srv;
+
+pub use to_clt::*;
+pub use to_srv::*;
diff --git a/src/to_clt.rs b/src/to_clt.rs
new file mode 100644 (file)
index 0000000..6a4b3c6
--- /dev/null
@@ -0,0 +1,330 @@
+use crate::*;
+
+#[mt_derive(to = "clt")]
+pub struct ArgbColor {
+    pub a: u8,
+    pub r: u8,
+    pub g: u8,
+    pub b: u8,
+}
+
+#[mt_derive(to = "clt", repr = "u8")]
+pub enum ModChanSig {
+    JoinOk = 0,
+    JoinFail,
+    LeaveOk,
+    LeaveFail,
+    NotRegistered,
+    SetState,
+}
+
+mod chat;
+mod env;
+mod hud;
+mod media;
+mod status;
+
+pub use chat::*;
+pub use env::*;
+pub use hud::*;
+pub use media::*;
+pub use status::*;
+
+#[mt_derive(to = "clt", repr = "u8", tag = "type", content = "data")]
+pub enum ToCltPkt {
+    Hello {
+        serialize_version: u8,
+        #[mt(const16 = 1)] // compression
+        proto_version: u16,
+        auth_methods: EnumSet<AuthMethod>,
+        username: String,
+    } = 2,
+    AcceptAuth {
+        player_pos: [f32; 3],
+        map_seed: u64,
+        send_interval: f32,
+        sudo_auth_methods: EnumSet<AuthMethod>,
+    } = 3,
+    AcceptSudoMode {
+        sudo_auth_methods: EnumSet<AuthMethod>,
+    } = 4,
+    DenySudoMode = 5,
+    Kick(KickReason) = 10,
+    BlockData {
+        pos: [i16; 3],
+        #[mt(zstd)]
+        block: Box<MapBlock>,
+    } = 32,
+    AddNode {
+        pos: [i16; 3],
+        param0: u16,
+        param1: u8,
+        param2: u8,
+        keep_meta: bool,
+    } = 33,
+    RemoveNode {
+        pos: [i16; 3],
+    } = 34,
+    Inv {
+        inv: String,
+    } = 39,
+    TimeOfDay {
+        time: u16,
+        speed: f32,
+    } = 41,
+    CsmRestrictionFlags {
+        flags: EnumSet<CsmRestrictionFlag>,
+        map_range: u32,
+    } = 42,
+    AddPlayerVelocity {
+        vel: [f32; 3],
+    } = 43,
+    MediaPush {
+        no_len_hash: String,
+        filename: String,
+        callback_token: u32,
+        should_cache: bool,
+    } = 44,
+    ChatMsg {
+        #[mt(const8 = 1)]
+        msg_type: ChatMsgType,
+        #[mt(utf16)]
+        sender: String,
+        #[mt(utf16)]
+        text: String,
+        timestamp: i64, // unix time
+    } = 47,
+    ObjRemoveAdd {
+        remove: Vec<u16>,
+        add: Vec<ObjAdd>,
+    } = 49,
+    ObjMsgs {
+        msgs: Vec<ObjMsg>,
+    } = 50,
+    Hp {
+        hp: u16,
+        #[mt(default)]
+        damage_effect: bool,
+    } = 51,
+    MovePlayer {
+        pos: [f32; 3],
+        pitch: f32,
+        yaw: f32,
+    } = 52,
+    LegacyKick {
+        #[mt(utf16)]
+        reason: String,
+    } = 53,
+    Fov {
+        fov: f32,
+        multiplier: bool,
+        transition_time: f32,
+    } = 54,
+    DeathScreen {
+        point_cam: bool,
+        point_at: [f32; 3],
+    } = 55,
+    Media {
+        n: u16,
+        i: u16,
+        files: Vec<MediaPayload>,
+    } = 56,
+    NodeDefs {
+        defs: Vec<NodeDef>,
+    } = 58,
+    AnnounceMedia {
+        files: Vec<MediaAnnounce>,
+        url: String,
+    } = 60,
+    #[mt(size32, zlib)]
+    ItemDefs {
+        #[mt(const8 = 0)] // version
+        defs: Vec<ItemDef>,
+        aliases: HashMap<String, String>,
+    } = 61,
+    PlaySound {
+        id: u32,
+        name: String,
+        gain: f32,
+        src_type: SoundSrcType,
+        pos: [f32; 3],
+        src_obj_id: u16,
+        #[serde(rename = "loop")]
+        sound_loop: bool,
+        fade: f32,
+        pitch: f32,
+        ephermeral: bool,
+    } = 63,
+    StopSound {
+        id: u32,
+    } = 64,
+    Privs {
+        privs: Vec<String>,
+    } = 65,
+    InvFormspec {
+        #[mt(size32)]
+        formspec: String,
+    } = 66,
+    DetachedInv {
+        name: String,
+        keep: bool,
+        len: u16,
+        #[mt(len0)]
+        inv: String,
+    } = 67,
+    ShowFormspec {
+        #[mt(len32)]
+        formspec: String,
+        formname: String,
+    } = 68,
+    Movement {
+        default_accel: f32,
+        air_accel: f32,
+        fast_accel: f32,
+        walk_speed: f32,
+        crouch_speed: f32,
+        fast_speed: f32,
+        climb_speed: f32,
+        jump_speed: f32,
+        gravity: f32,
+    } = 69,
+    SpawnParticle {
+        pos: [f32; 3],
+        vel: [f32; 3],
+        acc: [f32; 3],
+        expiration_time: f32,
+        size: f32,
+        collide: bool,
+        #[mt(len32)]
+        texture: String,
+        vertical: bool,
+        collision_rm: bool,
+        anim_params: TileAnim,
+        glow: u8,
+        obj_collision: bool,
+        node_param0: u16,
+        node_param2: u8,
+        node_tile: u8,
+    } = 70,
+    AddParticleSpawner {
+        amount: u16,
+        duration: f32,
+        pos: [[f32; 3]; 2],
+        vel: [[f32; 3]; 2],
+        acc: [[f32; 3]; 2],
+        expiration_time: [f32; 2],
+        size: [f32; 2],
+        collide: bool,
+        #[mt(len32)]
+        texture: String,
+        id: u32,
+        vertical: bool,
+        collision_rm: bool,
+        attached_obj_id: u16,
+        anim_params: TileAnim,
+        glow: u8,
+        obj_collision: bool,
+        node_param0: u16,
+        node_param2: u8,
+        node_tile: u8,
+    } = 71,
+    AddHud {
+        id: u32,
+        hud: HudElement,
+    } = 73,
+    RemoveHud {
+        id: u32,
+    } = 74,
+    ChangeHud {
+        id: u32,
+        change: HudChange,
+    } = 75,
+    HudFlags {
+        flags: EnumSet<HudFlag>,
+        mask: EnumSet<HudFlag>,
+    } = 76,
+    SetHotbarParam(HotbarParam) = 77,
+    Breath {
+        breath: u16,
+    } = 78,
+    // TODO
+    SkyParams = 79,
+    OverrideDayNightRatio {
+        #[serde(rename = "override")]
+        ratio_override: bool,
+        ratio: u16,
+    } = 80,
+    LocalPlayerAnim {
+        idle: [i32; 2],
+        walk: [i32; 2],
+        dig: [i32; 2],
+        walk_dig: [i32; 2],
+        speed: f32,
+    } = 81,
+    EyeOffset {
+        first: [f32; 3],
+        third: [f32; 3],
+    } = 82,
+    RemoveParticleSpawner {
+        id: u32,
+    } = 83,
+    CloudParams {
+        density: f32,
+        diffuse_color: ArgbColor,
+        ambient_color: ArgbColor,
+        height: f32,
+        thickness: f32,
+        speed: [f32; 2],
+    } = 84,
+    FadeSound {
+        id: u32,
+        step: f32,
+        gain: f32,
+    } = 85,
+    UpdatePlayerList {
+        update_type: PlayerListUpdateType,
+        players: Vec<String>,
+    } = 86,
+    ModChanMsg {
+        channel: String,
+        sender: String,
+        msg: String,
+    } = 87,
+    ModChanSig {
+        signal: ModChanSig,
+        channel: String,
+    } = 88,
+    NodeMetasChanged(#[mt(size32)] HashMap<[i16; 3], NodeMeta>) = 89,
+    SunParams {
+        visible: bool,
+        texture: String,
+        tone_map: String,
+        rise: String,
+        rising: bool,
+        size: f32,
+    } = 90,
+    MoonParams {
+        visible: bool,
+        texture: String,
+        tone_map: String,
+        size: f32,
+    } = 91,
+    StarParams {
+        visible: bool,
+        texture: String,
+        tone_map: String,
+        size: f32,
+    } = 92,
+    SrpBytesSaltB {
+        salt: Vec<u8>,
+        b: Vec<u8>,
+    } = 96,
+    FormspecPrepend {
+        prepend: String,
+    } = 97,
+    MinimapModes {
+        #[mt(len = "modes")]
+        current: u16,
+        modes: Vec<MinimapMode>,
+    } = 98,
+}
diff --git a/src/to_clt/chat.rs b/src/to_clt/chat.rs
new file mode 100644 (file)
index 0000000..4d99853
--- /dev/null
@@ -0,0 +1,16 @@
+use super::*;
+
+#[mt_derive(to = "clt", repr = "u8")]
+pub enum ChatMsgType {
+    Raw = 0,
+    Normal,
+    Announce,
+    System,
+}
+
+#[mt_derive(to = "clt", repr = "u8")]
+pub enum PlayerListUpdateType {
+    Init = 0,
+    Add,
+    Remove,
+}
diff --git a/src/to_clt/env.rs b/src/to_clt/env.rs
new file mode 100644 (file)
index 0000000..f242298
--- /dev/null
@@ -0,0 +1,44 @@
+use super::*;
+
+#[mt_derive(to = "clt")]
+pub struct ObjAdd; // TODO
+
+#[mt_derive(to = "clt")]
+pub struct ObjMsg; // TODO
+
+#[mt_derive(to = "clt", repr = "u8", enumset)]
+pub enum MapBlockFlag {
+    IsUnderground = 0,
+    DayNightDiff,
+    LightExpired,
+    NotGenerated,
+}
+
+pub const ALWAYS_LIT_FROM: u16 = 0xf000;
+
+#[mt_derive(to = "clt")]
+pub struct MapBlock {
+    pub flags: EnumSet<MapBlockFlag>,
+    pub lit_from: u16,
+
+    #[mt(const8 = 2)]
+    #[serde(skip)]
+    pub param0_size: (),
+
+    #[mt(const8 = 2)]
+    #[serde(skip)]
+    pub param12_size: (),
+
+    #[serde(with = "serde_arrays")]
+    pub param_0: [u16; 4096],
+    #[serde(with = "serde_arrays")]
+    pub param_1: [u8; 4096],
+    #[serde(with = "serde_arrays")]
+    pub param_2: [u8; 4096],
+
+    pub node_metas: HashMap<u16, NodeMeta>,
+
+    #[mt(const8 = 2)]
+    #[serde(skip)]
+    pub version: (),
+}
diff --git a/src/to_clt/hud.rs b/src/to_clt/hud.rs
new file mode 100644 (file)
index 0000000..c7a206a
--- /dev/null
@@ -0,0 +1,124 @@
+use super::*;
+
+#[mt_derive(to = "clt", repr = "u32", enumset)]
+pub enum HudStyleFlag {
+    Bold,
+    Italic,
+    Mono,
+}
+
+#[mt_derive(to = "clt", repr = "u8", tag = "attribute", content = "value")]
+pub enum HudChange {
+    Pos([f32; 2]) = 0,
+    Name(String),
+    Scale([f32; 2]),
+    Text(String),
+    Number(u32),
+    Item(u32),
+    Dir(u32),
+    Align([f32; 2]),
+    Offset([f32; 2]),
+    WorldPos([f32; 3]),
+    ZIndex(i32),
+    Text2(String),
+    Style(EnumSet<HudStyleFlag>),
+}
+
+#[mt_derive(to = "clt", repr = "u8")]
+pub enum HudType {
+    Image = 0,
+    Text,
+    Statbar,
+    Inv,
+    Waypoint,
+    ImageWaypoint,
+}
+
+#[mt_derive(to = "clt")]
+pub struct HudElement {
+    pub hud_type: HudType,
+    pub pos: [f32; 2],
+    pub name: String,
+    pub scale: [f32; 2],
+    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 z_index: i32,
+    pub text_2: String,
+    pub style: EnumSet<HudStyleFlag>,
+}
+
+impl HudElement {
+    pub fn apply_change(&mut self, change: HudChange) {
+        use HudChange::*;
+
+        match change {
+            Pos(v) => self.pos = v,
+            Name(v) => self.name = v,
+            Scale(v) => self.scale = v,
+            Text(v) => self.text = v,
+            Number(v) => self.number = v,
+            Item(v) => self.item = v,
+            Dir(v) => self.dir = v,
+            Align(v) => self.align = v,
+            Offset(v) => self.offset = v,
+            WorldPos(v) => self.world_pos = v,
+            ZIndex(v) => self.z_index = v,
+            Text2(v) => self.text_2 = v,
+            Style(v) => self.style = v,
+        }
+    }
+}
+
+#[mt_derive(to = "clt", repr = "u32", enumset)]
+pub enum HudFlag {
+    Hotbar,
+    HealthBar,
+    Crosshair,
+    WieldedItem,
+    BreathBar,
+    Minimap,
+    RadarMinimap,
+}
+
+#[mt_derive(to = "clt", repr = "u16", tag = "attribute", content = "value")]
+pub enum HotbarParam {
+    Size(#[mt(const16 = 4)] u32) = 0,
+    Image(String),
+    SelectionImage(String),
+}
+
+#[mt_derive(to = "clt", repr = "u16")]
+pub enum MinimapType {
+    None = 0,
+    Surface,
+    Radar,
+    Texture,
+}
+
+#[mt_derive(to = "clt")]
+pub struct MinimapMode {
+    pub minimap_type: MinimapType,
+    pub label: String,
+    pub size: u16,
+    pub texture: String,
+    pub scale: u16,
+}
+
+/*
+TODO: rustify
+
+var DefaultMinimap = []MinimapMode{
+    {Type: NoMinimap},
+    {Type: SurfaceMinimap, Size: 256},
+    {Type: SurfaceMinimap, Size: 128},
+    {Type: SurfaceMinimap, Size: 64},
+    {Type: RadarMinimap, Size: 512},
+    {Type: RadarMinimap, Size: 256},
+    {Type: RadarMinimap, Size: 128},
+}
+*/
diff --git a/src/to_clt/media.rs b/src/to_clt/media.rs
new file mode 100644 (file)
index 0000000..0dd1f3d
--- /dev/null
@@ -0,0 +1,33 @@
+use super::*;
+
+#[mt_derive(to = "clt")]
+pub struct MediaAnnounce {
+    pub name: String,
+    pub base64_sha1: String,
+}
+
+#[mt_derive(to = "clt")]
+pub struct MediaPayload {
+    pub name: String,
+    #[mt(len32)]
+    pub data: Vec<u8>,
+}
+
+#[mt_derive(to = "clt")]
+pub struct TileAnim; // TODO
+
+#[mt_derive(to = "clt")]
+pub struct ItemDef; // TODO
+
+#[mt_derive(to = "clt")]
+pub struct NodeDef; // TODO
+
+#[mt_derive(to = "clt")]
+pub struct NodeMeta; // TODO
+
+#[mt_derive(to = "clt", repr = "u16")]
+pub enum SoundSrcType {
+    Nowhere = 0,
+    Pos,
+    Obj,
+}
diff --git a/src/to_clt/status.rs b/src/to_clt/status.rs
new file mode 100644 (file)
index 0000000..54adb45
--- /dev/null
@@ -0,0 +1,80 @@
+use super::*;
+
+#[mt_derive(to = "clt", repr = "u8", tag = "reason")]
+pub enum KickReason {
+    WrongPasswd,
+    UnexpectedData,
+    SrvIsSingleplayer,
+    UnsupportedVersion,
+    BadNameChars,
+    BadName,
+    TooManyClts,
+    EmptyPasswd,
+    AlreadyConnected,
+    SrvErr,
+    Custom { custom: String },
+    Shutdown { custom: String, reconnect: bool },
+    Crash { custom: String, reconnect: bool },
+}
+
+impl KickReason {
+    pub fn reconnect(&self) -> bool {
+        use KickReason::*;
+
+        match self {
+            Shutdown { reconnect, .. } | Crash { reconnect, .. } => *reconnect,
+            _ => false,
+        }
+    }
+}
+
+impl fmt::Display for KickReason {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use KickReason::*;
+
+        match self {
+            WrongPasswd => write!(f, "wrong password"),
+            UnexpectedData => write!(f, "unexpected data"),
+            SrvIsSingleplayer => write!(f, "server is singleplayer"),
+            UnsupportedVersion => write!(f, "unsupported client version"),
+            BadNameChars => write!(f, "disallowed character(s) in player name"),
+            BadName => write!(f, "disallowed player name"),
+            TooManyClts => write!(f, "too many clients"),
+            EmptyPasswd => write!(f, "empty password"),
+            AlreadyConnected => write!(f, "another client is already connected with the same name"),
+            SrvErr => write!(f, "unsupported client version"),
+            Custom { custom } => write!(f, "{custom}"),
+            Shutdown { custom, .. } => {
+                if custom.is_empty() {
+                    write!(f, "server shutdown")
+                } else {
+                    write!(f, "server shutdown: {custom}")
+                }
+            }
+            Crash { custom, .. } => {
+                if custom.is_empty() {
+                    write!(f, "server crash")
+                } else {
+                    write!(f, "server crash: {custom}")
+                }
+            }
+        }
+    }
+}
+
+#[mt_derive(to = "clt", repr = "u32", enumset)]
+pub enum AuthMethod {
+    LegacyPasswd,
+    Srp,
+    FirstSrp,
+}
+
+#[mt_derive(to = "clt", repr = "u64", enumset)]
+pub enum CsmRestrictionFlag {
+    NoCsms,
+    NoChatMsgs,
+    NoItemDefs,
+    NoNodeDefs,
+    LimitMapRange,
+    NoPlayerList,
+}
diff --git a/src/to_srv.rs b/src/to_srv.rs
new file mode 100644 (file)
index 0000000..354bbb4
--- /dev/null
@@ -0,0 +1,119 @@
+use crate::*;
+
+#[mt_derive(to = "srv")]
+pub struct PlayerPos {
+    #[mt(const_u16 = 1)] // supported compression
+    pub pos_100: [i32; 3],
+    pub vel_100: [i32; 3],
+    pub pitch_100: i32,
+    pub yaw_100: i32,
+    pub fov_80: u8,
+    pub wanted_range: u8,
+}
+
+#[mt_derive(to = "srv", repr = "u8")]
+pub enum Interaction {
+    Dig = 0,
+    StopDigging,
+    Dug,
+    Place,
+    Use,
+    Activate,
+}
+
+#[mt_derive(to = "srv")]
+pub struct PointedThing; // TODO
+
+#[mt_derive(to = "srv", repr = "u16", tag = "type", content = "data")]
+pub enum ToSrvPkt {
+    Nil = 0,
+    Init {
+        serialize_version: u8,
+        #[mt(const_u16 = 1)] // supported compression
+        min_proto_version: u16,
+        max_proto_version: u16,
+        player_name: String,
+        #[mt(default)]
+        send_full_item_meta: bool,
+    } = 2,
+    Init2 {
+        lang: String,
+    } = 17,
+    JoinModChan {
+        channel: String,
+    } = 23,
+    LeaveModChan {
+        channel: String,
+    } = 24,
+    MsgModChan {
+        channel: String,
+        msg: String,
+    } = 25,
+    PlayerPos(PlayerPos) = 35,
+    GotBlocks {
+        #[mt(len8)]
+        blocks: Vec<[i16; 3]>,
+    } = 36,
+    DeletedBlocks {
+        #[mt(len8)]
+        blocks: Vec<[i16; 3]>,
+    } = 37,
+    InvAction {
+        #[mt(len0)]
+        action: String,
+    } = 49,
+    ChatMsg {
+        #[mt(utf16)]
+        msg: String,
+    } = 50,
+    FallDmg {
+        amount: u16,
+    } = 53,
+    SelectItem {
+        select_item: u16,
+    } = 55,
+    Respawn = 56,
+    Interact {
+        action: Interaction,
+        item_slot: u16,
+        #[mt(size_u32)]
+        pointed: PointedThing,
+        pos: PlayerPos,
+    } = 57,
+    RemovedSounds {
+        ids: Vec<i32>,
+    } = 58,
+    NodeMetaFields {
+        pos: [i16; 3],
+        formname: String,
+        fields: HashMap<String, String>,
+    } = 59,
+    InvFields {
+        formname: String,
+        fields: HashMap<String, String>,
+    } = 60,
+    ReqMedia {
+        filenames: Vec<String>,
+    } = 64,
+    CltReady {
+        major: u8,
+        minor: u8,
+        patch: u8,
+        reserved: u8,
+        version: String,
+        formspec: u16,
+    } = 67,
+    FirstSrp {
+        salt: Vec<u8>,
+        verifier: Vec<u8>,
+        empty_passwd: bool,
+    } = 80,
+    SrpBytesA {
+        a: Vec<u8>,
+        no_sha1: bool,
+    } = 81,
+    SrpBytesM {
+        m: Vec<u8>,
+    } = 82,
+    Disco = 0xffff,
+}