~starkingdoms/starkingdoms

219a8aa32f506daf623f910f24991c6826e7c159 — core 2 years ago 07e8dcf + ffbbb9a
Merge branch 'feat/protobuf-protocol' into 'master'

Rewrite the protocol to use protobuf

See merge request starkingdoms.tk/starkingdoms.tk!1
M Cargo.lock => Cargo.lock +319 -18
@@ 9,6 9,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"

[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
 "memchr",
]

[[package]]
name = "anyhow"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"

[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 169,6 184,12 @@ dependencies = [
]

[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"

[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 336,6 357,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"

[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
 "errno-dragonfly",
 "libc",
 "windows-sys 0.48.0",
]

[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
 "cc",
 "libc",
]

[[package]]
name = "exr"
version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 352,6 394,15 @@ dependencies = [
]

[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
 "instant",
]

[[package]]
name = "fdeflate"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 573,6 624,12 @@ dependencies = [
]

[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"

[[package]]
name = "http"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 670,6 727,26 @@ dependencies = [
]

[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
 "cfg-if",
]

[[package]]
name = "io-lifetimes"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
 "hermit-abi 0.3.1",
 "libc",
 "windows-sys 0.48.0",
]

[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 718,6 795,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"

[[package]]
name = "linux-raw-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"

[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1022,6 1105,94 @@ dependencies = [
]

[[package]]
name = "protobuf"
version = "2.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"

[[package]]
name = "protobuf"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e"
dependencies = [
 "once_cell",
 "protobuf-support",
 "thiserror",
]

[[package]]
name = "protobuf-codegen"
version = "2.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6"
dependencies = [
 "protobuf 2.28.0",
]

[[package]]
name = "protobuf-codegen"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd418ac3c91caa4032d37cb80ff0d44e2ebe637b2fb243b6234bf89cdac4901"
dependencies = [
 "anyhow",
 "once_cell",
 "protobuf 3.2.0",
 "protobuf-parse",
 "regex",
 "tempfile",
 "thiserror",
]

[[package]]
name = "protobuf-parse"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d39b14605eaa1f6a340aec7f320b34064feb26c93aec35d6a9a2272a8ddfa49"
dependencies = [
 "anyhow",
 "indexmap",
 "log",
 "protobuf 3.2.0",
 "protobuf-support",
 "tempfile",
 "thiserror",
 "which",
]

[[package]]
name = "protobuf-support"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372"
dependencies = [
 "thiserror",
]

[[package]]
name = "protoc"
version = "2.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0218039c514f9e14a5060742ecd50427f8ac4f85a6dc58f2ddb806e318c55ee"
dependencies = [
 "log",
 "which",
]

[[package]]
name = "protoc-rust"
version = "2.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f8a182bb17c485f20bdc4274a8c39000a61024cfe461c799b50fec77267838"
dependencies = [
 "protobuf 2.28.0",
 "protobuf-codegen 2.28.0",
 "protoc",
 "tempfile",
]

[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1119,6 1290,32 @@ dependencies = [
]

[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
 "bitflags",
]

[[package]]
name = "regex"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-syntax",
]

[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"

[[package]]
name = "rmp"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1173,6 1370,20 @@ dependencies = [
]

[[package]]
name = "rustix"
version = "0.37.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
dependencies = [
 "bitflags",
 "errno",
 "io-lifetimes",
 "libc",
 "linux-raw-sys",
 "windows-sys 0.48.0",
]

[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1358,8 1569,9 @@ dependencies = [
name = "starkingdoms-protocol"
version = "0.1.0"
dependencies = [
 "rmp-serde",
 "serde",
 "protobuf 3.2.0",
 "protobuf-codegen 3.2.0",
 "protoc-rust",
]

[[package]]


@@ 1373,7 1585,6 @@ dependencies = [
 "log",
 "nalgebra",
 "rapier2d-f64",
 "rmp-serde",
 "serde",
 "serde_json",
 "simple_logger",


@@ 1406,6 1617,19 @@ dependencies = [
]

[[package]]
name = "tempfile"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
dependencies = [
 "cfg-if",
 "fastrand",
 "redox_syscall",
 "rustix",
 "windows-sys 0.45.0",
]

[[package]]
name = "thiserror"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1746,6 1970,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"

[[package]]
name = "which"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
dependencies = [
 "either",
 "libc",
 "once_cell",
]

[[package]]
name = "wide"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 1783,13 2018,13 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
 "windows_aarch64_gnullvm",
 "windows_aarch64_msvc",
 "windows_i686_gnu",
 "windows_i686_msvc",
 "windows_x86_64_gnu",
 "windows_x86_64_gnullvm",
 "windows_x86_64_msvc",
 "windows_aarch64_gnullvm 0.42.2",
 "windows_aarch64_msvc 0.42.2",
 "windows_i686_gnu 0.42.2",
 "windows_i686_msvc 0.42.2",
 "windows_x86_64_gnu 0.42.2",
 "windows_x86_64_gnullvm 0.42.2",
 "windows_x86_64_msvc 0.42.2",
]

[[package]]


@@ 1798,7 2033,16 @@ version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
 "windows-targets",
 "windows-targets 0.42.2",
]

[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
 "windows-targets 0.48.0",
]

[[package]]


@@ 1807,13 2051,28 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
 "windows_aarch64_gnullvm",
 "windows_aarch64_msvc",
 "windows_i686_gnu",
 "windows_i686_msvc",
 "windows_x86_64_gnu",
 "windows_x86_64_gnullvm",
 "windows_x86_64_msvc",
 "windows_aarch64_gnullvm 0.42.2",
 "windows_aarch64_msvc 0.42.2",
 "windows_i686_gnu 0.42.2",
 "windows_i686_msvc 0.42.2",
 "windows_x86_64_gnu 0.42.2",
 "windows_x86_64_gnullvm 0.42.2",
 "windows_x86_64_msvc 0.42.2",
]

[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
 "windows_aarch64_gnullvm 0.48.0",
 "windows_aarch64_msvc 0.48.0",
 "windows_i686_gnu 0.48.0",
 "windows_i686_msvc 0.48.0",
 "windows_x86_64_gnu 0.48.0",
 "windows_x86_64_gnullvm 0.48.0",
 "windows_x86_64_msvc 0.48.0",
]

[[package]]


@@ 1823,42 2082,84 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"

[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"

[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"

[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"

[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"

[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"

[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"

[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"

[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"

[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"

[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"

[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

[[package]]
name = "ws_stream_wasm"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"

M client/src/chat.rs => client/src/chat.rs +7 -3
@@ 1,7 1,9 @@
use std::error::Error;
use wasm_bindgen::prelude::*;
use starkingdoms_protocol::MessageC2S;
use crate::CLIENT;
use futures::SinkExt;
use starkingdoms_protocol::message_c2s::MessageC2SChat;

#[wasm_bindgen]
// TODO: Switch to async-aware mutexes


@@ 10,9 12,11 @@ pub async fn send_chat(message: &str) -> Result<(), JsError> {
    let client_data = &mut CLIENT.write()?.client_data;

    if let Some(data) = client_data {
        send!(data.tx, &MessageC2S::Chat {
            message: message.to_string()
        }).await?;
        let msg = MessageC2S::Chat(MessageC2SChat {
            message: message.to_string(),
            special_fields: Default::default(),
        }).try_into().map_err(|e: Box<dyn Error> | JsError::new(&e.to_string()))?;
        send!(data.tx, msg).await?;
    } else {
        return Err(JsError::new("Client not yet connected to server"));
    }

D client/src/input/mod.rs => client/src/input/mod.rs +0 -37
@@ 1,37 0,0 @@
use std::error::Error;
use async_trait::async_trait;

/*
Functions the input subsystem needs to perform:
- init
- register and unregister event handlers for:
    - mouse movement
    - keydown, keyup, keypress
    - clicks
 */

#[async_trait]
pub trait InputSubsystem {
    async fn init() -> Result<Self, Box<dyn Error>> where Self: Sized;

    async fn register_on_mouse_move(&mut self, id: &str, callback: dyn FnMut(OnMouseMoveEvent)) -> Result<(), Box<dyn Error>>;
    async fn unregister_on_mouse_move(&mut self, id: &str) -> Result<(), Box<dyn Error>>;

    async fn register_on_key_down(&mut self, id: &str, callback: dyn FnMut(OnKeyDownEvent)) -> Result<(), Box<dyn Error>>;
    async fn unregister_on_key_down(&mut self, id: &str) -> Result<(), Box<dyn Error>>;

    async fn register_on_key_up(&mut self, id: &str, callback: dyn FnMut(OnKeyUpEvent)) -> Result<(), Box<dyn Error>>;
    async fn unregister_on_key_up(&mut self, id: &str) -> Result<(), Box<dyn Error>>;

    async fn register_on_keypress(&mut self, id: &str, callback: dyn FnMut(OnKeypressEvent)) -> Result<(), Box<dyn Error>>;
    async fn unregister_on_keypress(&mut self, id: &str) -> Result<(), Box<dyn Error>>;

    async fn register_on_click(&mut self, id: &str, callback: dyn FnMut(OnClickEvent)) -> Result<(), Box<dyn Error>>;
    async fn unregister_on_click(&mut self, id: &str) -> Result<(), Box<dyn Error>>;
}

pub struct OnMouseMoveEvent {}
pub struct OnKeyDownEvent {}
pub struct OnKeyUpEvent {}
pub struct OnKeypressEvent {}
pub struct OnClickEvent {}
\ No newline at end of file

M client/src/lib.rs => client/src/lib.rs +44 -40
@@ 1,27 1,26 @@
use std::error::Error;
use std::panic;
use std::str::FromStr;

use futures::stream::{SplitSink, SplitStream};
use futures::{StreamExt, TryFutureExt};
use log::{debug, error, info, Level, trace, warn};
use futures::{StreamExt};
use log::{error, info, Level, trace, warn};
use wasm_bindgen::prelude::*;
use web_sys::console::debug;
use ws_stream_wasm::{WsErr, WsMessage, WsMeta, WsStream};
use starkingdoms_protocol::{ProtocolPlanet, ProtocolPlayer, State};
use starkingdoms_protocol::PROTOCOL_VERSION;
use starkingdoms_protocol::MessageS2C;
use starkingdoms_protocol::MessageC2S;
use futures::SinkExt;
use lazy_static::lazy_static;
use std::sync::Arc;
use std::sync::RwLock;

use async_recursion::async_recursion;
use futures::FutureExt;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Window};
use starkingdoms_protocol::GoodbyeReason::PingPongTimeout;
use starkingdoms_protocol::message_c2s::{MessageC2SGoodbye, MessageC2SHello, MessageC2SPing};
use starkingdoms_protocol::{MessageC2S, MessageS2C, PROTOCOL_VERSION};
use starkingdoms_protocol::goodbye_reason::GoodbyeReason;
use starkingdoms_protocol::planet::Planet;
use starkingdoms_protocol::player::Player;
use starkingdoms_protocol::state::State;

use crate::rendering::Renderer;
use crate::rendering::renderer::WebRenderer;
use crate::textures::loader::TextureLoader;


@@ 33,7 32,6 @@ pub mod macros;
pub mod chat;
pub mod rendering;
pub mod textures;
pub mod input;

#[wasm_bindgen]
extern {


@@ 43,10 41,10 @@ extern {
#[derive(Debug)]
pub struct Client {
    pub client_data: Option<ClientData>,
    pub planets: Vec<ProtocolPlanet>,
    pub planets: Vec<Planet>,
    pub x: f64,
    pub y: f64,
    pub players: Vec<ProtocolPlayer>
    pub players: Vec<Player>
}

#[derive(Debug)]


@@ 70,6 68,7 @@ lazy_static! {
        y: 0f64,
        players: vec![]
    }));
    //pub static ref BUTTONS: Arc<Buttons> = Arc::new(Buttons { up: false, left: false, right: false, down: false });
}

pub const MAX_CONNECTION_TRIES: i32 = 10;


@@ 152,11 151,13 @@ pub async fn main(gateway: &str, username: &str, backoff: i32, textures: Texture

    set_status("Handshaking with server...");

    send!(client_data.tx, &MessageC2S::Hello {
        next_state: State::Play,
    let msg = MessageC2S::Hello(MessageC2SHello {
        version: PROTOCOL_VERSION,
        requested_username: username.to_string()
    }).await?;
        requested_username: username.to_string(),
        next_state: State::Play.into(),
        special_fields: Default::default(),
    }).try_into()?;
    send!(client_data.tx, msg).await?;

    trace!("Sent handshake start packet");



@@ 164,13 165,13 @@ pub async fn main(gateway: &str, username: &str, backoff: i32, textures: Texture
        let typed_msg: MessageS2C = msg;

        match typed_msg {
            MessageS2C::Hello { version, given_username, next_state } => {
                info!("FAST CONNECT - connected to server protocol {} given username {}, switching to state {:?}", version, given_username, next_state);
                client_data.state = next_state;
            MessageS2C::Hello(pkt) => {
                info!("FAST CONNECT - connected to server protocol {} given username {}, switching to state {:?}", pkt.version, pkt.given_username, pkt.next_state);
                client_data.state = pkt.next_state.unwrap();
            },
            MessageS2C::Goodbye { reason } => {
                error!("server disconnected before finishing handshake: {:?}", reason);
                return Err(format!("disconnected by server: {:?}", reason).into());
            MessageS2C::Goodbye(pkt) => {
                error!("server disconnected before finishing handshake: {:?}", pkt.reason);
                return Err(format!("disconnected by server: {:?}", pkt.reason).into());
            },
            _ => {
                warn!("received unexpected packet from server: {:?}", typed_msg);


@@ 197,7 198,8 @@ pub async fn send_ping_pong() -> Result<(), JsError> {

    let client_data = client.client_data.as_mut().unwrap();

    send!(client_data.tx, &MessageC2S::Ping {}).await?;

    send!(client_data.tx, MessageC2S::Ping(MessageC2SPing {special_fields: Default::default()}).try_into().map_err(|_| JsError::new("I/O Error"))?).await?;

    Ok(())
}


@@ 215,9 217,11 @@ pub async fn update_socket() -> Result<(), JsError> {

    if client_data.pong_timeout < (js_sys::Date::now() as u64 / 1000) {
        error!("Connection timed out");
        send!(client_data.tx, &MessageC2S::Goodbye {
                reason: PingPongTimeout
            }).await?;
        let msg = MessageC2S::Goodbye(MessageC2SGoodbye {
            reason: GoodbyeReason::PingPongTimeout.into(),
            special_fields: Default::default(),
        }).try_into().map_err(|e: Box<dyn Error> | JsError::new(&e.to_string()))?;
        send!(client_data.tx, msg).await?;
        client.client_data = None;
        set_status("Connection timed out. Reload to reconnect");
        return Err(JsError::new("Connection timed out"));


@@ 232,13 236,13 @@ pub async fn update_socket() -> Result<(), JsError> {

    if let Some(msg) = maybe_msg {
        match msg {
            MessageS2C::Goodbye { reason } => {
                info!("server sent disconnect: {:?}", reason);
            MessageS2C::Goodbye(pkt) => {
                info!("server sent disconnect: {:?}", pkt.reason);
                client.client_data = None;
                return Err(JsError::new("disconnected by server"));
            }
            MessageS2C::Chat { from, message } => {
                info!("[CHAT] {}: {}", from, message);
            MessageS2C::Chat(pkt) => {
                info!("[CHAT] {}: {}", pkt.from, pkt.message);

                let window: Window = web_sys::window().expect("no global `window` exists");
                let document = window.document().expect("should have a document on window");


@@ 246,25 250,25 @@ pub async fn update_socket() -> Result<(), JsError> {

                let new_elem = document.create_element("div").expect("could not create element");

                let msg_formatted = markdown::to_html(&format!("**[{}]** {}", from, message));
                let msg_formatted = markdown::to_html(&format!("**[{}]** {}", pkt.from, pkt.message));

                new_elem.set_inner_html(&msg_formatted);

                chatbox.append_child(&new_elem).unwrap();
            },
            MessageS2C::Pong {} => {
            MessageS2C::Pong(_) => {
                client_data.pong_timeout = (js_sys::Date::now() as u64 / 1000) + PONG_MAX_TIMEOUT
            },
            MessageS2C::PlanetData { planets } => {
                client.planets = planets;
            MessageS2C::PlanetData(pkt) => {
                client.planets = pkt.planets;
            },
            MessageS2C::PlayersUpdate { players } => {
                let me = players.iter().find(|i| i.username == client_data.username);
            MessageS2C::PlayersUpdate(pkt) => {
                let me = pkt.players.iter().find(|i| i.username == client_data.username);
                if let Some(me) = me {
                    client.x = me.x;
                    client.y = me.y;
                    client.x = me.x as f64;
                    client.y = me.y as f64;
                }
                client.players = players;
                client.players = pkt.players;
            }
            _ => {
                warn!("server sent unexpected packet {:?}, ignoring", msg);

M client/src/macros.rs => client/src/macros.rs +5 -6
@@ 1,10 1,9 @@
use serde::{Serialize};
use ws_stream_wasm::WsMessage;

#[macro_export]
macro_rules! send {
    ($writer:expr,$pkt:expr) => {
        $writer.send($crate::macros::__generic_packet_to_message($pkt).unwrap())
        $writer.send($crate::macros::__generic_packet_to_message($pkt))
    };
}



@@ 15,7 14,7 @@ macro_rules! recv {
            if let Some(future_result) = $reader.next().now_or_never() {
                if let Some(msg) = future_result {
                    if let WsMessage::Binary(msg) = msg {
                        match rmp_serde::from_slice(&msg) {
                        match MessageS2C::try_from(msg.as_slice()) {
                            Ok(d) => Ok(Some(d)),
                            Err(e) => {
                                log::error!("error deserializing message: {}", e);


@@ 42,7 41,7 @@ macro_rules! recv_now {
        {
            if let Some(msg) = $reader.next().await {
                if let WsMessage::Binary(msg) = msg {
                    match rmp_serde::from_slice(&msg) {
                    match MessageS2C::try_from(msg.as_slice()) {
                        Ok(d) => Ok(Some(d)),
                        Err(e) => {
                            log::error!("error deserializing message: {}", e);


@@ 60,6 59,6 @@ macro_rules! recv_now {
    };
}

pub fn __generic_packet_to_message<T: Serialize>(pkt: &T) -> Result<WsMessage, rmp_serde::encode::Error> {
    rmp_serde::to_vec(&pkt).map(WsMessage::from)
pub fn __generic_packet_to_message(pkt: Vec<u8>) -> WsMessage {
    WsMessage::from(pkt)
}
\ No newline at end of file

M client/src/rendering/renderer_playercentric.rs => client/src/rendering/renderer_playercentric.rs +7 -7
@@ 1,11 1,11 @@
use std::error::Error;
use async_trait::async_trait;
use log::debug;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlImageElement};
use crate::rendering::Renderer;
use wasm_bindgen::{JsCast, JsValue};
use crate::CLIENT;
use crate::textures::TextureManager;

// TODO: Remove all the f32s

pub const STARFIELD_RENDER_SCALE: f64 = 1.0;



@@ 35,7 35,7 @@ impl Renderer for WebRenderer {
        if client.client_data.is_none() {
            return Err("client not yet initialized".into());
        }
        let client_data = client.client_data.as_ref().unwrap();
        let _client_data = client.client_data.as_ref().unwrap();

        //let camera_translate_x = -client.x + (typed_canvas_element.width() / 2) as f64;
        let viewer_size_x = typed_canvas_element.width() as f64;


@@ 58,10 58,10 @@ impl Renderer for WebRenderer {
            //context.set_transform(1f64, 0f64, 0f64, 1f64, 0f64, 0f64).map_err(|e: JsValue| e.as_string().unwrap())?;
            //context.translate(-planet.x, -planet.y).map_err(|e: JsValue| e.as_string().unwrap())?;

            let texture_image = document.get_element_by_id(&format!("tex-{}", planet.planet_type.as_texture_id())).unwrap().dyn_into::<HtmlImageElement>().unwrap();
            let texture_image = document.get_element_by_id(&format!("tex-{}", planet.planet_type.unwrap().as_texture_id())).unwrap().dyn_into::<HtmlImageElement>().unwrap();
            // pos:
            //debug!("P {} {}", planet.x - planet.radius - client.x, planet.y - planet.radius - client.y);
            context.draw_image_with_html_image_element_and_dw_and_dh(&texture_image, planet.x - planet.radius - client.x, planet.y - planet.radius - client.y, planet.radius * 2f64, planet.radius * 2f64).map_err(|e: JsValue| e.as_string().unwrap())?;
            context.draw_image_with_html_image_element_and_dw_and_dh(&texture_image, (planet.x - planet.radius - client.x as f32) as f64, (planet.y - planet.radius - client.y as f32) as f64, planet.radius as f64 * 2f64, planet.radius as f64 * 2f64).map_err(|e: JsValue| e.as_string().unwrap())?;

            //context.restore();
        }


@@ 71,14 71,14 @@ impl Renderer for WebRenderer {

            //context.translate(player.x, player.y).map_err(|e: JsValue| e.as_string().unwrap())?;
            //gaah fuck why noo i didnt want this. godforsaken canvas rotation
            context.translate(player.x - client.x, player.y - client.y).map_err(|e: JsValue| e.as_string().unwrap())?; // fwip
            context.translate(player.x as f64 - client.x, player.y as f64 - client.y).map_err(|e: JsValue| e.as_string().unwrap())?; // fwip

            context.set_text_align("center");
            context.set_font("30px Segoe UI");
            context.set_fill_style(&JsValue::from_str("white")); // CssStyleColor
            context.fill_text(&player.username, 0f64, -35f64).map_err(|e: JsValue| e.as_string().unwrap())?;

            context.rotate(player.rotation).map_err(|e: JsValue| e.as_string().unwrap())?; // fwip
            context.rotate(player.rotation as f64).map_err(|e: JsValue| e.as_string().unwrap())?; // fwip

            let texture_image = document.get_element_by_id("tex-hearty").unwrap().dyn_into::<HtmlImageElement>().unwrap();
            //context.draw_image_with_html_image_element_and_dw_and_dh(&texture_image, player.x - 25f64 - client.x, player.y - 25f64 - client.y, 50f64, 50f64).map_err(|e: JsValue| e.as_string().unwrap())?;

M protocol/Cargo.toml => protocol/Cargo.toml +5 -2
@@ 6,5 6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1", features = ["derive"] }
rmp-serde = "1.1"
\ No newline at end of file
protobuf = "3"

[build-dependencies]
protobuf-codegen = "3"
protoc-rust = "^2.0"
\ No newline at end of file

A protocol/build.rs => protocol/build.rs +13 -0
@@ 0,0 1,13 @@
fn main() {
    protobuf_codegen::Codegen::new()
        .cargo_out_dir("protos")
        .include("src/pbuf")
        .input("src/pbuf/starkingdoms-protocol.proto")
        .input("src/pbuf/message_c2s.proto")
        .input("src/pbuf/message_s2c.proto")
        .input("src/pbuf/planet.proto")
        .input("src/pbuf/player.proto")
        .input("src/pbuf/state.proto")
        .input("src/pbuf/goodbye_reason.proto")
        .run_from_script();
}
\ No newline at end of file

A protocol/src/legacy.rs => protocol/src/legacy.rs +103 -0
@@ 0,0 1,103 @@
use serde::{Deserialize, Serialize};

pub const PROTOCOL_VERSION: u32 = 1;

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum State {
    Handshake,
    Play
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum MessageC2S {
    Hello {
        version: u32,
        requested_username: String,
        next_state: State
    },

    Goodbye {
        reason: GoodbyeReason
    },

    Chat {
        message: String
    },

    Ping {},
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum MessageS2C {
    Hello {
        version: u32,
        given_username: String,
        next_state: State
    },

    Goodbye {
        reason: GoodbyeReason
    },

    Chat {
        from: String,
        message: String
    },

    Pong {},

    PlayersUpdate {
        players: Vec<ProtocolPlayer>
    },


    PlanetData {
        planets: Vec<ProtocolPlanet>
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProtocolPlayer {
    pub rotation: f64,
    pub x: f64,
    pub y: f64,
    pub username: String
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum GoodbyeReason {
    UnsupportedProtocol { supported: u32, got: u32 },
    UnexpectedPacket,
    UnexpectedNextState,
    UsernameTaken,
    PingPongTimeout,
    Done,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProtocolPlanet {
    pub planet_type: PlanetType,
    pub x: f64,
    pub y: f64,
    pub radius: f64
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum PlanetType {
    Earth
}

impl PlanetType {
    pub fn as_texture_id(&self) -> String {
        match self {
            PlanetType::Earth => "earth".to_string()
        }
    }
}

pub fn pc2s(pkt: &MessageC2S) -> Vec<u8> {
    rmp_serde::to_vec(pkt).unwrap()
}
pub fn ps2c(pkt: &MessageS2C) -> Vec<u8> {
    rmp_serde::to_vec(pkt).unwrap()
}

M protocol/src/lib.rs => protocol/src/lib.rs +135 -81
@@ 1,103 1,157 @@
use serde::{Deserialize, Serialize};
use std::error::Error;
use protobuf::{Enum, Message};
use crate::message_c2s::{MessageC2SChat, MessageC2SGoodbye, MessageC2SHello, MessageC2SPing};
use crate::message_s2c::{MessageS2CChat, MessageS2CGoodbye, MessageS2CHello, MessageS2CPlanetData, MessageS2CPlayersUpdate, MessageS2CPong};
use crate::planet::PlanetType;
use crate::starkingdoms_protocol::PacketWrapper;
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));

pub const PROTOCOL_VERSION: u32 = 1;

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum State {
    Handshake,
    Play
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Debug)]
pub enum MessageC2S {
    Hello {
        version: u32,
        requested_username: String,
        next_state: State
    },

    Goodbye {
        reason: GoodbyeReason
    },

    Chat {
        message: String
    },

    Ping {},
    Hello(MessageC2SHello),
    Goodbye(MessageC2SGoodbye),
    Chat(MessageC2SChat),
    Ping(MessageC2SPing)
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Debug)]
pub enum MessageS2C {
    Hello {
        version: u32,
        given_username: String,
        next_state: State
    },

    Goodbye {
        reason: GoodbyeReason
    },

    Chat {
        from: String,
        message: String
    },

    Pong {},

    PlayersUpdate {
        players: Vec<ProtocolPlayer>
    },


    PlanetData {
        planets: Vec<ProtocolPlanet>
    }
    Hello(MessageS2CHello),
    Goodbye(MessageS2CGoodbye),
    Chat(MessageS2CChat),
    Pong(MessageS2CPong),
    PlayersUpdate(MessageS2CPlayersUpdate),
    PlanetData(MessageS2CPlanetData)
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProtocolPlayer {
    pub rotation: f64,
    pub x: f64,
    pub y: f64,
    pub username: String
impl TryFrom<&[u8]> for MessageC2S {
    type Error = Box<dyn Error>;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        let pkt = starkingdoms_protocol::PacketWrapper::parse_from_bytes(value)?;

        let deser_pkt = match pkt.packet_id {
            _id if _id == message_c2s::message_c2shello::Packet_info::type_.value() as i64 => {
                MessageC2S::Hello(MessageC2SHello::parse_from_bytes(&pkt.packet_data)?)
            },
            _id if _id == message_c2s::message_c2sgoodbye::Packet_info::type_.value() as i64 => {
                MessageC2S::Goodbye(MessageC2SGoodbye::parse_from_bytes(&pkt.packet_data)?)
            },
            _id if _id == message_c2s::message_c2schat::Packet_info::type_.value() as i64 => {
                MessageC2S::Chat(MessageC2SChat::parse_from_bytes(&pkt.packet_data)?)
            },
            _id if _id == message_c2s::message_c2sping::Packet_info::type_.value() as i64 => {
                MessageC2S::Ping(MessageC2SPing::parse_from_bytes(&pkt.packet_data)?)
            }
            _ => { return Err("Not a C2S packet".into()); }
        };

        Ok(deser_pkt)
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum GoodbyeReason {
    UnsupportedProtocol { supported: u32, got: u32 },
    UnexpectedPacket,
    UnexpectedNextState,
    UsernameTaken,
    PingPongTimeout,
    Done,
impl TryInto<Vec<u8>> for MessageC2S {
    type Error = Box<dyn Error>;

    fn try_into(self) -> Result<Vec<u8>, Self::Error> {
        let (pkt_id, pkt_bytes) = match self {
            MessageC2S::Hello(p) => {
                (message_c2s::message_c2shello::Packet_info::type_.value(), p.write_to_bytes()?)
            }
            MessageC2S::Goodbye(p) => {
                (message_c2s::message_c2sgoodbye::Packet_info::type_.value(), p.write_to_bytes()?)
            }
            MessageC2S::Chat(p) => {
                (message_c2s::message_c2schat::Packet_info::type_.value(), p.write_to_bytes()?)
            }
            MessageC2S::Ping(p) => {
                (message_c2s::message_c2sping::Packet_info::type_.value(), p.write_to_bytes()?)
            }
        };

        let pkt = PacketWrapper {
            packet_id: pkt_id as i64,
            packet_data: pkt_bytes,
            special_fields: Default::default(),
        };

        Ok(pkt.write_to_bytes()?)
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProtocolPlanet {
    pub planet_type: PlanetType,
    pub x: f64,
    pub y: f64,
    pub radius: f64
impl TryFrom<&[u8]> for MessageS2C {
    type Error = Box<dyn Error>;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        let pkt = PacketWrapper::parse_from_bytes(value)?;

        let deser_pkt = match pkt.packet_id {
            _id if _id == message_s2c::message_s2chello::Packet_info::type_.value() as i64 => {
                MessageS2C::Hello(MessageS2CHello::parse_from_bytes(&pkt.packet_data)?)
            },
            _id if _id == message_s2c::message_s2cgoodbye::Packet_info::type_.value() as i64 => {
                MessageS2C::Goodbye(MessageS2CGoodbye::parse_from_bytes(&pkt.packet_data)?)
            },
            _id if _id == message_s2c::message_s2cchat::Packet_info::type_.value() as i64 => {
                MessageS2C::Chat(MessageS2CChat::parse_from_bytes(&pkt.packet_data)?)
            },
            _id if _id == message_s2c::message_s2cpong::Packet_info::type_.value() as i64 => {
                MessageS2C::Pong(MessageS2CPong::parse_from_bytes(&pkt.packet_data)?)
            },
            _id if _id == message_s2c::message_s2cplayers_update::Packet_info::type_.value() as i64 => {
                MessageS2C::PlayersUpdate(MessageS2CPlayersUpdate::parse_from_bytes(&pkt.packet_data)?)
            },
            _id if _id == message_s2c::message_s2cplanet_data::Packet_info::type_.value() as i64 => {
                MessageS2C::PlanetData(MessageS2CPlanetData::parse_from_bytes(&pkt.packet_data)?)
            },
            _ => { return Err("Not a S2C packet".into()); }
        };

        Ok(deser_pkt)
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum PlanetType {
    Earth
impl TryInto<Vec<u8>> for MessageS2C {
    type Error = Box<dyn Error>;

    fn try_into(self) -> Result<Vec<u8>, Self::Error> {
        let (pkt_id, pkt_bytes) = match self {
            MessageS2C::Hello(p) => {
                (message_s2c::message_s2chello::Packet_info::type_.value(), p.write_to_bytes()?)
            }
            MessageS2C::Goodbye(p) => {
                (message_s2c::message_s2cgoodbye::Packet_info::type_.value(), p.write_to_bytes()?)
            }
            MessageS2C::Chat(p) => {
                (message_s2c::message_s2cchat::Packet_info::type_.value(), p.write_to_bytes()?)
            }
            MessageS2C::Pong(p) => {
                (message_s2c::message_s2cpong::Packet_info::type_.value(), p.write_to_bytes()?)
            }
            MessageS2C::PlayersUpdate(p) => {
                (message_s2c::message_s2cplayers_update::Packet_info::type_.value(), p.write_to_bytes()?)
            }
            MessageS2C::PlanetData(p) => {
                (message_s2c::message_s2cplanet_data::Packet_info::type_.value(), p.write_to_bytes()?)
            }
        };

        let pkt = PacketWrapper {
            packet_id: pkt_id as i64,
            packet_data: pkt_bytes,
            special_fields: Default::default(),
        };

        Ok(pkt.write_to_bytes()?)
    }
}

impl PlanetType {
impl planet::PlanetType {
    pub fn as_texture_id(&self) -> String {
        match self {
            PlanetType::Earth => "earth".to_string()
        }
    }
}

pub fn pc2s(pkt: &MessageC2S) -> Vec<u8> {
    rmp_serde::to_vec(pkt).unwrap()
}
pub fn ps2c(pkt: &MessageS2C) -> Vec<u8> {
    rmp_serde::to_vec(pkt).unwrap()
}
}
\ No newline at end of file

A protocol/src/pbuf/goodbye_reason.proto => protocol/src/pbuf/goodbye_reason.proto +11 -0
@@ 0,0 1,11 @@
syntax = "proto3";
package protocol.goodbye_reason;

enum GoodbyeReason {
  UnsupportedProtocol = 0;
  UnexpectedPacket = 1;
  UnexpectedNextState = 2;
  UsernameTaken = 3;
  PingPongTimeout = 4;
  Done = 5;
}
\ No newline at end of file

A protocol/src/pbuf/message_c2s.proto => protocol/src/pbuf/message_c2s.proto +29 -0
@@ 0,0 1,29 @@
syntax = "proto3";
package protocol.message_c2s;

import "state.proto";
import "goodbye_reason.proto";

message MessageC2SHello {
  enum packet_info { unknown = 0; type = 0x01; }

  uint32 version = 1;             // Version of the protocol. Currently always 1
  string requested_username = 2;  // The username that the client is requesting.
  protocol.state.State next_state = 3;           // The state the connection will go into after the handshake.
}

message MessageC2SGoodbye {
  enum packet_info { unknown = 0; type = 0x02; }

  protocol.goodbye_reason.GoodbyeReason reason = 1; // The reason the client is disconnecting the server
}

message MessageC2SChat {
  enum packet_info { unknown = 0; type = 0x03; }

  string message = 1; // The chat message to sent
}

message MessageC2SPing {
  enum packet_info { unknown = 0; type = 0x04; }
}
\ No newline at end of file

A protocol/src/pbuf/message_s2c.proto => protocol/src/pbuf/message_s2c.proto +44 -0
@@ 0,0 1,44 @@
syntax = "proto3";
package protocol.message_s2c;

import "state.proto";
import "goodbye_reason.proto";
import "player.proto";
import "planet.proto";

message MessageS2CHello {
  enum packet_info { unknown = 0; type = 0x05; }

  uint32 version = 1;         // The version of the protocol in use. Currently always 1.
  string given_username = 2;  // The username actually assigned to the player
  protocol.state.State next_state = 3;       // The state to switch the game into
}

message MessageS2CGoodbye {
  enum packet_info { unknown = 0; type = 0x06; }

  protocol.goodbye_reason.GoodbyeReason reason = 1; // The reason for the disconnect
}

message MessageS2CChat {
  enum packet_info { unknown = 0; type = 0x07; }

  string from = 1;    // The username of the player who sent the message
  string message = 2; // The contents of the chat message
}

message MessageS2CPong {
  enum packet_info { unknown = 0; type = 0x08; }
}

message MessageS2CPlayersUpdate {
  enum packet_info { unknown = 0; type = 0x09; }

  repeated protocol.player.Player players = 1;  // List of all players in the server
}

message MessageS2CPlanetData {
  enum packet_info { unknown = 0; type = 0x0a; }

  repeated protocol.planet.Planet planets = 1;  // List of all planets on the server
}
\ No newline at end of file

A protocol/src/pbuf/planet.proto => protocol/src/pbuf/planet.proto +13 -0
@@ 0,0 1,13 @@
syntax = "proto3";
package protocol.planet;

message Planet {
  PlanetType planet_type = 1;  // Type of the planet
  float x = 2;          // Translation on the X axis, in game units
  float y = 3;          // Translation on the Y axis, in game units
  float radius = 4;     // The radius of the planet extending out from (x, y)
}

enum PlanetType {
  Earth = 0;
}
\ No newline at end of file

A protocol/src/pbuf/player.proto => protocol/src/pbuf/player.proto +9 -0
@@ 0,0 1,9 @@
syntax = "proto3";
package protocol.player;

message Player {
  float rotation = 1;   // The rotation, clockwise, in degrees, of the player
  float x = 2;          // The translation on the X axis, in game units, of the player
  float y = 3;          // The translation on the Y axis, in game units, of the player
  string username = 4;  // The username of the player
}
\ No newline at end of file

A protocol/src/pbuf/starkingdoms-protocol.proto => protocol/src/pbuf/starkingdoms-protocol.proto +14 -0
@@ 0,0 1,14 @@
syntax = "proto3";
package protocol;

import public "message_c2s.proto";
import public "state.proto";
import public "goodbye_reason.proto";
import public "message_s2c.proto";
import public "player.proto";
import public "planet.proto";

message PacketWrapper {
  int64 packet_id = 1;    // What is the Packet ID of this packet?
  bytes packet_data = 2;  // Protobuf-encoded bytearray containing the actual packet
}
\ No newline at end of file

A protocol/src/pbuf/state.proto => protocol/src/pbuf/state.proto +7 -0
@@ 0,0 1,7 @@
syntax = "proto3";
package protocol.state;

enum State {
  Handshake = 0;
  Play = 1;
}
\ No newline at end of file

M server/Cargo.toml => server/Cargo.toml +0 -1
@@ 12,7 12,6 @@ slp-description = "A StarKingdoms.TK server"
[dependencies]
tokio = { version = "1.27", features = ["macros", "sync", "rt-multi-thread"] }
serde = { version = "1", features = ["derive"] }
rmp-serde = { version = "1" }
serde_json = "1"
futures = { version = "0.3", default-features = false }
hyper = { version = "0.14", features = ["server", "http1", "http2", "tcp"] }

M server/src/handler.rs => server/src/handler.rs +79 -56
@@ 12,8 12,10 @@ use tokio::sync::RwLock;
use tokio::sync::mpsc::Receiver;
use tokio_tungstenite::WebSocketStream;
use tungstenite::Message;
use starkingdoms_protocol::{GoodbyeReason, MessageC2S, MessageS2C, PROTOCOL_VERSION, State};
use starkingdoms_protocol::GoodbyeReason::PingPongTimeout;
use starkingdoms_protocol::goodbye_reason::GoodbyeReason;
use starkingdoms_protocol::message_s2c::{MessageS2CChat, MessageS2CGoodbye, MessageS2CHello, MessageS2CPlanetData, MessageS2CPlayersUpdate, MessageS2CPong};
use starkingdoms_protocol::{MessageS2C, MessageC2S, PROTOCOL_VERSION};
use starkingdoms_protocol::state::State;
use crate::manager::{ClientHandlerMessage, ClientManager, PhysicsData, Player};
use crate::{send, recv, SCALE};



@@ 29,24 31,30 @@ pub async fn handle_client(mgr: ClientManager, data: Arc<RwLock<PhysicsData>>, r
                ClientHandlerMessage::Tick => {} // this intentionally does nothing,
                ClientHandlerMessage::ChatMessage { from, message } => {
                    if matches!(state, State::Play) {
                        send!(client_tx, &MessageS2C::Chat {
                        let msg = MessageS2C::Chat(MessageS2CChat {
                            from,
                            message,
                            from
                        }).await?;
                            special_fields: Default::default(),
                        }).try_into()?;
                        send!(client_tx, msg).await?;
                    }
                }
                ClientHandlerMessage::PlayersUpdate { players } => {
                    if matches!(state, State::Play) {
                        send!(client_tx, &MessageS2C::PlayersUpdate {
                            players
                        }).await?;
                        let msg = MessageS2C::PlayersUpdate(MessageS2CPlayersUpdate {
                            players,
                            special_fields: Default::default(),
                        }).try_into()?;
                        send!(client_tx, msg).await?;
                    }
                }
                ClientHandlerMessage::PlanetData { planets } => {
                    if matches!(state, State::Play) {
                        send!(client_tx, &MessageS2C::PlanetData {
                            planets
                        }).await?;
                        let msg = MessageS2C::PlanetData(MessageS2CPlanetData {
                            planets,
                            special_fields: Default::default(),
                        }).try_into()?;
                        send!(client_tx, msg).await?;
                    }
                }
            }


@@ 57,9 65,11 @@ pub async fn handle_client(mgr: ClientManager, data: Arc<RwLock<PhysicsData>>, r

        if ping_timeout < SystemTime::now() {
            error!("[{}] ping timeout", remote_addr);
            send!(client_tx, &MessageS2C::Goodbye {
                reason: PingPongTimeout
            }).await?;
            let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                reason: GoodbyeReason::PingPongTimeout.into(),
                special_fields: Default::default(),
            }).try_into()?;
            send!(client_tx, msg).await?;
            break;
        }



@@ 67,51 77,57 @@ pub async fn handle_client(mgr: ClientManager, data: Arc<RwLock<PhysicsData>>, r
            match state {
                State::Handshake => {
                    match pkt {
                        MessageC2S::Hello { version, requested_username, next_state } => {
                            if !matches!(next_state, State::Play) {
                                error!("client sent unexpected state {:?} (expected: Play)", next_state);
                                send!(client_tx, &MessageS2C::Goodbye {
                                        reason: GoodbyeReason::UnexpectedNextState,
                                    }).await?;
                        MessageC2S::Hello(pkt) => {
                            if !matches!(pkt.next_state.unwrap(), State::Play) {
                                error!("client sent unexpected state {:?} (expected: Play)", pkt.next_state);
                                let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                                    reason: GoodbyeReason::UnexpectedNextState.into(),
                                    special_fields: Default::default(),
                                }).try_into()?;
                                send!(client_tx, msg).await?;
                                break;
                            }

                            // check version
                            if version != PROTOCOL_VERSION {
                                error!("client sent incompatible version {} (expected: {})", version, PROTOCOL_VERSION);
                                send!(client_tx, &MessageS2C::Goodbye {
                                        reason: GoodbyeReason::UnsupportedProtocol {
                                            supported: PROTOCOL_VERSION,
                                            got: version,
                                        },
                                    }).await?;
                            if pkt.version != PROTOCOL_VERSION {
                                error!("client sent incompatible version {} (expected: {})", pkt.version, PROTOCOL_VERSION);
                                let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                                    reason: GoodbyeReason::UnsupportedProtocol.into(),
                                    special_fields: Default::default(),
                                }).try_into()?;
                                send!(client_tx, msg).await?;
                                break;
                            }

                            // determine if we can give them that username
                            {
                                if mgr.usernames.read().await.values().any(|u| *u == requested_username) {
                                    error!("client requested username {} but it is in use", requested_username);
                                    send!(client_tx, &MessageS2C::Goodbye {
                                            reason: GoodbyeReason::UsernameTaken,
                                        }).await?;
                                if mgr.usernames.read().await.values().any(|u| *u == pkt.requested_username) {
                                    error!("client requested username {} but it is in use", pkt.requested_username);
                                    let msg: Vec<u8> = MessageS2C::Goodbye(MessageS2CGoodbye {
                                        reason: GoodbyeReason::UsernameTaken.into(),
                                        special_fields: Default::default(),
                                    }).try_into()?;
                                    send!(client_tx, msg).await?;
                                    break;
                                }
                            }

                            // username is fine
                            {
                                mgr.usernames.write().await.insert(remote_addr, requested_username.clone());
                                mgr.usernames.write().await.insert(remote_addr, pkt.requested_username.clone());
                            }

                            send!(client_tx, &MessageS2C::Hello {
                                    version,
                                    given_username: requested_username.clone(),
                                    next_state,
                                }).await?;
                            let msg = MessageS2C::Hello(MessageS2CHello {
                                version: pkt.version,
                                given_username: pkt.requested_username.clone(),
                                special_fields: Default::default(),
                                next_state: pkt.next_state,
                            }).try_into()?;

                            state = next_state;
                            username = requested_username;
                            send!(client_tx, msg).await?;

                            state = pkt.next_state.unwrap();
                            username = pkt.requested_username;

                            // make player rigidbody
                            {


@@ 134,15 150,17 @@ pub async fn handle_client(mgr: ClientManager, data: Arc<RwLock<PhysicsData>>, r
                                mgr.players.write().await.insert(remote_addr, Player { handle: player_handle });
                            }
                        },
                        MessageC2S::Goodbye { reason } => {
                            info!("client sent goodbye: {:?}", reason);
                        MessageC2S::Goodbye(pkt) => {
                            info!("client sent goodbye: {:?}", pkt.reason);
                            break;
                        },
                        _ => {
                            error!("client sent unexpected packet {:?} for state {:?}", pkt, state);
                            send!(client_tx, &MessageS2C::Goodbye {
                                    reason: GoodbyeReason::UnexpectedPacket,
                                }).await?;
                            let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                                reason: GoodbyeReason::UnexpectedPacket.into(),
                                special_fields: Default::default(),
                            }).try_into()?;
                            send!(client_tx, msg).await?;
                            break;
                        }
                    }


@@ 151,20 169,22 @@ pub async fn handle_client(mgr: ClientManager, data: Arc<RwLock<PhysicsData>>, r
                    match pkt {
                        MessageC2S::Hello { .. } => {
                            error!("client sent unexpected packet {:?} for state {:?}", pkt, state);
                            send!(client_tx, &MessageS2C::Goodbye {
                                    reason: GoodbyeReason::UnexpectedPacket,
                                }).await?;
                            let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                                reason: GoodbyeReason::UnexpectedPacket.into(),
                                special_fields: Default::default(),
                            }).try_into()?;
                            send!(client_tx, msg).await?;
                            break;
                        },
                        MessageC2S::Goodbye { reason } => {
                            info!("client sent goodbye: {:?}", reason);
                        MessageC2S::Goodbye(pkt) => {
                            info!("client sent goodbye: {:?}", pkt.reason);
                            break;
                        },
                        MessageC2S::Chat { message } => {
                            info!("[{}] CHAT: [{}] {}", remote_addr, username, message);
                        MessageC2S::Chat(pkt) => {
                            info!("[{}] CHAT: [{}] {}", remote_addr, username, pkt.message);

                            for (_addr, client_thread) in mgr.handlers.read().await.iter() {
                                match client_thread.tx.send(ClientHandlerMessage::ChatMessage { from: username.clone(), message: message.clone() }).await {
                                match client_thread.tx.send(ClientHandlerMessage::ChatMessage { from: username.clone(), message: pkt.message.clone() }).await {
                                    Ok(_) => (),
                                    Err(e) => {
                                        error!("unable to update a client thread: {}", e);


@@ 172,8 192,11 @@ pub async fn handle_client(mgr: ClientManager, data: Arc<RwLock<PhysicsData>>, r
                                }
                            }
                        },
                        MessageC2S::Ping {} => {
                            send!(client_tx, &MessageS2C::Pong {}).await?;
                        MessageC2S::Ping(_) => {
                            let msg = MessageS2C::Pong(MessageS2CPong {
                                special_fields: Default::default(),
                            }).try_into()?;
                            send!(client_tx, msg).await?;
                            ping_timeout = SystemTime::now() + Duration::from_secs(10);
                        }
                    }

M server/src/macros.rs => server/src/macros.rs +7 -8
@@ 1,13 1,16 @@
use serde::{Serialize};
use tungstenite::Message;

#[macro_export]
macro_rules! send {
    ($writer:expr,$pkt:expr) => {
        $writer.send($crate::macros::__generic_packet_to_message($pkt).unwrap())
        $writer.send($crate::macros::_generic_pkt_into($pkt))
    };
}

pub fn _generic_pkt_into(p: Vec<u8>) -> Message {
    Message::from(p)
}

#[macro_export]
macro_rules! recv {
    ($reader:expr) => {


@@ 17,7 20,7 @@ macro_rules! recv {
                    match msg {
                        Ok(msg) => {
                            if msg.is_binary() {
                                match rmp_serde::from_slice(&msg.into_data()) {
                                match MessageC2S::try_from(msg.into_data().as_slice()) {
                                    Ok(d) => Ok(Some(d)),
                                    Err(e) => {
                                        log::error!("error deserializing message: {}", e);


@@ 52,7 55,7 @@ macro_rules! recv_now {
                match msg {
                    Ok(msg) => {
                        if msg.is_binary() {
                            match rmp_serde::from_slice(&msg.into_data()) {
                            match MessageC2S::try_from(&msg.into_data()) {
                                Ok(d) => Ok(Some(d)),
                                Err(e) => {
                                    log::error!("error deserializing message: {}", e);


@@ 74,8 77,4 @@ macro_rules! recv_now {
            }
        }
    };
}

pub fn __generic_packet_to_message<T: Serialize>(pkt: &T) -> Result<Message, rmp_serde::encode::Error> {
    rmp_serde::to_vec(&pkt).map(Message::from)
}
\ No newline at end of file

M server/src/main.rs => server/src/main.rs +1 -1
@@ 6,7 6,7 @@ use hyper::service::{make_service_fn, service_fn};
use manager::PhysicsData;
use nalgebra::vector;
use planet::Planets;
use rapier2d_f64::prelude::{MultibodyJointSet, ImpulseJointSet, ColliderSet, RigidBodySet, NarrowPhase, BroadPhase, IslandManager, CCDSolver, IntegrationParameters, ColliderBuilder, RigidBodyBuilder, RigidBodyHandle};
use rapier2d_f64::prelude::{MultibodyJointSet, ImpulseJointSet, ColliderSet, RigidBodySet, NarrowPhase, BroadPhase, IslandManager, CCDSolver, IntegrationParameters};
use tokio_tungstenite::WebSocketStream;
use tungstenite::{handshake};
use futures::stream::StreamExt;

M server/src/manager.rs => server/src/manager.rs +2 -5
@@ 1,11 1,8 @@
use std::collections::HashMap;

use std::net::SocketAddr;
use std::sync::Arc;

use rapier2d_f64::na::{Vector2};
use rapier2d_f64::prelude::{IntegrationParameters, PhysicsPipeline, IslandManager, BroadPhase, NarrowPhase, ImpulseJointSet, MultibodyJointSet, CCDSolver, RigidBodySet, ColliderSet, RigidBodyHandle};
use starkingdoms_protocol::{ProtocolPlanet, ProtocolPlayer};
use tokio::sync::mpsc::Sender;
use tokio::sync::RwLock;



@@ 61,6 58,6 @@ impl PhysicsData {
pub enum ClientHandlerMessage {
    Tick,
    ChatMessage { from: String, message: String },
    PlayersUpdate { players: Vec<ProtocolPlayer> },
    PlanetData { planets: Vec<ProtocolPlanet> },
    PlayersUpdate { players: Vec<starkingdoms_protocol::player::Player> },
    PlanetData { planets: Vec<starkingdoms_protocol::planet::Planet> },
}

M server/src/planet.rs => server/src/planet.rs +8 -6
@@ 1,7 1,7 @@
use log::debug;
use nalgebra::{Vector2, vector};
use rapier2d_f64::prelude::{RigidBodyHandle, RigidBodySet, ColliderBuilder, RigidBodyBuilder, ColliderSet};
use starkingdoms_protocol::{PlanetType, ProtocolPlanet};
use starkingdoms_protocol::planet::PlanetType;

use crate::{SCALE, manager::ClientHandlerMessage};



@@ 72,11 72,13 @@ impl Planets {
        let mut planets = vec![];

        for planet in self.planets.clone() {
            planets.push(ProtocolPlanet {
                planet_type: planet.planet_type,
                x: planet.position.0 * SCALE,
                y: planet.position.1 * SCALE,
                radius: planet.radius, // DO NOT * SCALE
            // TODO: Adjust codegen to use f64
            planets.push(starkingdoms_protocol::planet::Planet {
                planet_type: planet.planet_type.into(),
                x: (planet.position.0 * SCALE) as f32,
                y: (planet.position.1 * SCALE) as f32,
                radius: planet.radius as f32, // DO NOT * SCALE
                special_fields: Default::default(),
            });
        }


M server/src/timer.rs => server/src/timer.rs +9 -9
@@ 1,11 1,9 @@

use std::{time::Duration, sync::Arc};
use log::{error, debug};

use log::{error};
use nalgebra::vector;
use rapier2d_f64::prelude::{PhysicsPipeline, RigidBodyHandle};
use starkingdoms_protocol::{ProtocolPlanet, PlanetType, ProtocolPlayer};
use rapier2d_f64::prelude::{PhysicsPipeline};
use tokio::{time::sleep, sync::RwLock};
use starkingdoms_protocol::player::Player;
use crate::{manager::{ClientHandlerMessage, ClientManager, PhysicsData}, SCALE, planet::Planets};

pub async fn timer_main(mgr: ClientManager, physics_data: Arc<RwLock<PhysicsData>>, world_data: Arc<RwLock<Planets>>) {


@@ 35,11 33,13 @@ pub async fn timer_main(mgr: ClientManager, physics_data: Arc<RwLock<PhysicsData
                    username = usernames.get(player_id).unwrap().clone();
                }

                protocol_players.push(ProtocolPlayer {
                    rotation: rotation.angle(),
                    x: translation.x * SCALE,
                    y: translation.y * SCALE,
                // TODO: Figure out how to adjust codegen to use f64
                protocol_players.push(Player {
                    rotation: rotation.angle() as f32,
                    x: (translation.x * SCALE) as f32,
                    y: (translation.y * SCALE) as f32,
                    username,
                    special_fields: Default::default(),
                });
            }
        }