~starkingdoms/starkingdoms

fe715d008ae189135c3d309d1f30c834f03348cb — core 2 years ago 9a16ae1
make pretty pt2
M api/src/config.rs => api/src/config.rs +22 -12
@@ 1,9 1,9 @@
use log::error;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use log::error;
use once_cell::sync::Lazy;
use serde::{Serialize, Deserialize};

pub static CONFIG: Lazy<StarkingdomsApiConfig> = Lazy::new(|| {
    let config_str = match fs::read_to_string("/etc/starkingdoms/config.toml") {


@@ 31,7 31,7 @@ pub struct StarkingdomsApiConfig {
    pub jwt_signing_secret: String,
    pub base: String,
    pub game: String,
    pub realms: HashMap<String, StarkingdomsApiConfigRealm>
    pub realms: HashMap<String, StarkingdomsApiConfigRealm>,
}

#[derive(Serialize, Deserialize, Debug)]


@@ 50,13 50,13 @@ pub struct StarkingdomsApiConfigDatabase {
    #[serde(default = "time_defaults")]
    pub max_lifetime: u64,
    #[serde(default = "sqlx_logging_default")]
    pub sqlx_logging: bool
    pub sqlx_logging: bool,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct StarkingdomsApiConfigServer {
    #[serde(default = "socketaddr_8080")]
    pub bind: SocketAddr
    pub bind: SocketAddr,
}

/*


@@ 69,11 69,21 @@ issuer = "https://api.e3t.cc"
pub struct StarkingdomsApiConfigRealm {
    pub authorize_url: String,
    pub public_key: String,
    pub issuer: String
    pub issuer: String,
}

fn max_connections_default() -> u32 { 100 }
fn min_connections_default() -> u32 { 5 }
fn time_defaults() -> u64 { 8 }
fn sqlx_logging_default() -> bool { true }
fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) }
\ No newline at end of file
fn max_connections_default() -> u32 {
    100
}
fn min_connections_default() -> u32 {
    5
}
fn time_defaults() -> u64 {
    8
}
fn sqlx_logging_default() -> bool {
    true
}
fn socketaddr_8080() -> SocketAddr {
    SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080))
}

M api/src/error.rs => api/src/error.rs +45 -57
@@ 1,9 1,9 @@
use actix_web::error::{JsonPayloadError, PayloadError};
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct APIErrorsResponse {
    pub errors: Vec<APIError>
    pub errors: Vec<APIError>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct APIError {


@@ 11,10 11,12 @@ pub struct APIError {
    pub message: String,
    #[serde(skip_serializing_if = "is_none")]
    #[serde(default)]
    pub path: Option<String>
    pub path: Option<String>,
}

fn is_none<T>(o: &Option<T>) -> bool { o.is_none() }
fn is_none<T>(o: &Option<T>) -> bool {
    o.is_none()
}

impl From<&JsonPayloadError> for APIError {
    fn from(value: &JsonPayloadError) -> Self {


@@ 71,58 73,44 @@ impl From<&JsonPayloadError> for APIError {
impl From<&PayloadError> for APIError {
    fn from(value: &PayloadError) -> Self {
        match value {
            PayloadError::Incomplete(e) => {
                APIError {
                    code: "ERR_UNEXPECTED_EOF".to_string(),
                    message: match e {
                        None => "Payload reached EOF but was incomplete".to_string(),
                        Some(e) => format!("Payload reached EOF but was incomplete: {}", e)
                    },
                    path: None,
                }
            }
            PayloadError::EncodingCorrupted => {
                APIError {
                    code: "ERR_CORRUPTED_PAYLOAD".to_string(),
                    message: "Payload content encoding corrupted".to_string(),
                    path: None,
                }
            }
            PayloadError::Overflow => {
                APIError {
                    code: "ERR_PAYLOAD_OVERFLOW".to_string(),
                    message: "Payload reached size limit".to_string(),
                    path: None,
                }
            }
            PayloadError::UnknownLength => {
                APIError {
                    code: "ERR_PAYLOAD_UNKNOWN_LENGTH".to_string(),
                    message: "Unable to determine payload length".to_string(),
                    path: None,
                }
            }
            PayloadError::Http2Payload(e) => {
                APIError {
                    code: "ERR_HTTP2_ERROR".to_string(),
                    message: format!("HTTP/2 error: {}", e),
                    path: None,
                }
            }
            PayloadError::Io(e) => {
                APIError {
                    code: "ERR_IO_ERROR".to_string(),
                    message: format!("I/O error: {}", e),
                    path: None,
                }
            }
            _ => {
                APIError {
                    code: "ERR_UNKNOWN_ERROR".to_string(),
                    message: "An unknown error has occured".to_string(),
                    path: None,
                }
            }
            PayloadError::Incomplete(e) => APIError {
                code: "ERR_UNEXPECTED_EOF".to_string(),
                message: match e {
                    None => "Payload reached EOF but was incomplete".to_string(),
                    Some(e) => format!("Payload reached EOF but was incomplete: {}", e),
                },
                path: None,
            },
            PayloadError::EncodingCorrupted => APIError {
                code: "ERR_CORRUPTED_PAYLOAD".to_string(),
                message: "Payload content encoding corrupted".to_string(),
                path: None,
            },
            PayloadError::Overflow => APIError {
                code: "ERR_PAYLOAD_OVERFLOW".to_string(),
                message: "Payload reached size limit".to_string(),
                path: None,
            },
            PayloadError::UnknownLength => APIError {
                code: "ERR_PAYLOAD_UNKNOWN_LENGTH".to_string(),
                message: "Unable to determine payload length".to_string(),
                path: None,
            },
            PayloadError::Http2Payload(e) => APIError {
                code: "ERR_HTTP2_ERROR".to_string(),
                message: format!("HTTP/2 error: {}", e),
                path: None,
            },
            PayloadError::Io(e) => APIError {
                code: "ERR_IO_ERROR".to_string(),
                message: format!("I/O error: {}", e),
                path: None,
            },
            _ => APIError {
                code: "ERR_UNKNOWN_ERROR".to_string(),
                message: "An unknown error has occured".to_string(),
                path: None,
            },
        }
    }
}
\ No newline at end of file
}

M api/src/main.rs => api/src/main.rs +21 -17
@@ 1,23 1,23 @@
use std::error::Error;
use std::time::Duration;
use crate::config::CONFIG;
use crate::error::{APIError, APIErrorsResponse};
use actix_request_identifier::RequestIdentifier;
use actix_web::{App, HttpResponse, HttpServer};
use actix_web::http::header::HeaderValue;
use actix_web::web::{Data, JsonConfig};
use actix_web::{App, HttpResponse, HttpServer};
use log::{info, Level};
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use tera::Tera;
use starkingdoms_api_migration::{Migrator, MigratorTrait};
use crate::config::CONFIG;
use crate::error::{APIError, APIErrorsResponse};
use std::error::Error;
use std::time::Duration;
use tera::Tera;

pub mod config;
pub mod routes;
pub mod error;
pub mod routes;

pub struct AppState {
    pub conn: DatabaseConnection,
    pub templates: Tera
    pub templates: Tera,
}

#[actix_web::main]


@@ 46,7 46,7 @@ async fn main() -> Result<(), Box<dyn Error>> {

    let data = Data::new(AppState {
        conn: db,
        templates: tera
        templates: tera,
    });

    HttpServer::new(move || {


@@ 57,19 57,23 @@ async fn main() -> Result<(), Box<dyn Error>> {
                actix_web::error::InternalError::from_response(
                    err,
                    HttpResponse::BadRequest().json(APIErrorsResponse {
                        errors: vec![
                            api_error
                        ],
                    })
                ).into()
                        errors: vec![api_error],
                    }),
                )
                .into()
            }))
            .wrap(RequestIdentifier::with_generator(|| {
                HeaderValue::from_str(&ulid::Ulid::new().to_string()).unwrap()
            }))
            .wrap(RequestIdentifier::with_generator(|| HeaderValue::from_str(&ulid::Ulid::new().to_string()).unwrap()))
            .service(routes::select_realm::select_realm)
            .service(routes::callback::callback)
            .service(routes::beamin::beam_in)
            .service(routes::beamout::beam_out)
            .service(actix_files::Files::new("/static", "static"))
    }).bind(CONFIG.server.bind)?.run().await?;
    })
    .bind(CONFIG.server.bind)?
    .run()
    .await?;

    Ok(())
}
\ No newline at end of file
}

M api/src/routes/beamin.rs => api/src/routes/beamin.rs +49 -54
@@ 1,6 1,8 @@
use std::collections::BTreeMap;
use actix_web::{HttpResponse, post};
use crate::config::CONFIG;
use crate::error::{APIError, APIErrorsResponse};
use crate::AppState;
use actix_web::web::{Data, Json};
use actix_web::{post, HttpResponse};
use hmac::digest::KeyInit;
use hmac::Hmac;
use jwt::VerifyWithKey;


@@ 9,35 11,31 @@ use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use starkingdoms_protocol::api::APISavedPlayerData;
use crate::AppState;
use crate::config::CONFIG;
use crate::error::{APIError, APIErrorsResponse};
use std::collections::BTreeMap;

#[derive(Serialize, Deserialize)]
pub struct BeaminRequest {
    pub api_token: String,
    pub user_auth_realm_id: String,
    pub user_auth_token: String
    pub user_auth_token: String,
}

#[derive(Serialize, Deserialize)]
pub struct BeaminResponse {
    pub save_id: String,
    pub save: APISavedPlayerData
    pub save: APISavedPlayerData,
}

#[post("/beamin")]
pub async fn beam_in(data: Json<BeaminRequest>, state: Data<AppState>) -> HttpResponse {
    if !CONFIG.internal_tokens.contains(&data.api_token) {
        return HttpResponse::Unauthorized().json(APIErrorsResponse {
            errors: vec![
                APIError {
                    code: "ERR_BAD_TOKEN".to_string(),
                    message: "Missing or invalid api token".to_string(),
                    path: None,
                }
            ],
        })
            errors: vec![APIError {
                code: "ERR_BAD_TOKEN".to_string(),
                message: "Missing or invalid api token".to_string(),
                path: None,
            }],
        });
    }

    let key: Hmac<Sha256> = Hmac::new_from_slice(CONFIG.jwt_signing_secret.as_bytes()).unwrap();


@@ 46,67 44,64 @@ pub async fn beam_in(data: Json<BeaminRequest>, state: Data<AppState>) -> HttpRe
        Err(e) => {
            error!("verifying error: {}", e);
            return HttpResponse::Unauthorized().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_BAD_TOKEN".to_string(),
                        message: "Missing or invalid user token".to_string(),
                        path: None,
                    }
                ],
            })
                errors: vec![APIError {
                    code: "ERR_BAD_TOKEN".to_string(),
                    message: "Missing or invalid user token".to_string(),
                    path: None,
                }],
            });
        }
    };

    if !token.contains_key("user") || !token.contains_key("nonce") {
        return HttpResponse::Unauthorized().json(APIErrorsResponse {
            errors: vec![
                APIError {
                    code: "ERR_BAD_TOKEN".to_string(),
                    message: "Missing or invalid user token (missing scopes)".to_string(),
                    path: None,
                }
            ],
            errors: vec![APIError {
                code: "ERR_BAD_TOKEN".to_string(),
                message: "Missing or invalid user token (missing scopes)".to_string(),
                path: None,
            }],
        });
    }

    let user_id = token.get("user").unwrap();

    let user_savefile: Vec<starkingdoms_api_entities::entity::user_savefile::Model> = match starkingdoms_api_entities::entity::user_savefile::Entity::find().filter(
        starkingdoms_api_entities::entity::user_savefile::Column::User.eq(user_id))
        .order_by_desc(starkingdoms_api_entities::entity::user_savefile::Column::Timestamp).all(&state.conn).await {
        Ok(sf) => sf,
        Err(e) => {
            error!("database error: {}", e);
            return HttpResponse::InternalServerError().json(APIErrorsResponse {
                errors: vec![
                    APIError {
    let user_savefile: Vec<starkingdoms_api_entities::entity::user_savefile::Model> =
        match starkingdoms_api_entities::entity::user_savefile::Entity::find()
            .filter(starkingdoms_api_entities::entity::user_savefile::Column::User.eq(user_id))
            .order_by_desc(starkingdoms_api_entities::entity::user_savefile::Column::Timestamp)
            .all(&state.conn)
            .await
        {
            Ok(sf) => sf,
            Err(e) => {
                error!("database error: {}", e);
                return HttpResponse::InternalServerError().json(APIErrorsResponse {
                    errors: vec![APIError {
                        code: "ERR_DB_ERROR".to_string(),
                        message: "Unable to fetch user savefiles".to_string(),
                        path: None,
                    }
                ],
            });
        }
    };
                    }],
                });
            }
        };
    if user_savefile.is_empty() {
        return HttpResponse::NoContent().json(APIErrorsResponse {
            errors: vec![
                APIError {
                    code: "ERR_NO_SAVES".to_string(),
                    message: "This user has no savefiles".to_string(),
                    path: None,
                }
            ],
            errors: vec![APIError {
                code: "ERR_NO_SAVES".to_string(),
                message: "This user has no savefiles".to_string(),
                path: None,
            }],
        });
    }

    let save = &user_savefile[0];
    let save_id = &save.id;
    let save_data_str = &save.data;
    let save_data: APISavedPlayerData = toml::from_str(save_data_str).expect("database contained corrupted player save data");
    let save_data: APISavedPlayerData =
        toml::from_str(save_data_str).expect("database contained corrupted player save data");

    HttpResponse::Ok().json(BeaminResponse {
        save_id: save_id.clone(),
        save: save_data
        save: save_data,
    })
}
\ No newline at end of file
}

M api/src/routes/beamout.rs => api/src/routes/beamout.rs +35 -40
@@ 1,7 1,8 @@
use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};
use actix_web::{HttpResponse, post};
use crate::config::CONFIG;
use crate::error::{APIError, APIErrorsResponse};
use crate::AppState;
use actix_web::web::{Data, Json};
use actix_web::{post, HttpResponse};
use hmac::digest::KeyInit;
use hmac::Hmac;
use jwt::VerifyWithKey;


@@ 9,18 10,17 @@ use log::error;
use sea_orm::{ActiveModelTrait, IntoActiveModel};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use ulid::Ulid;
use starkingdoms_protocol::api::APISavedPlayerData;
use crate::AppState;
use crate::config::CONFIG;
use crate::error::{APIError, APIErrorsResponse};
use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};
use ulid::Ulid;

#[derive(Serialize, Deserialize)]
pub struct BeamoutRequest {
    pub api_token: String,
    pub user_auth_realm_id: String,
    pub user_auth_token: String,
    pub data: APISavedPlayerData
    pub data: APISavedPlayerData,
}

#[derive(Serialize, Deserialize)]


@@ 30,14 30,12 @@ pub struct BeamoutResponse {}
pub async fn beam_out(data: Json<BeamoutRequest>, state: Data<AppState>) -> HttpResponse {
    if !CONFIG.internal_tokens.contains(&data.api_token) {
        return HttpResponse::Unauthorized().json(APIErrorsResponse {
            errors: vec![
                APIError {
                    code: "ERR_BAD_TOKEN".to_string(),
                    message: "Missing or invalid api token".to_string(),
                    path: None,
                }
            ],
        })
            errors: vec![APIError {
                code: "ERR_BAD_TOKEN".to_string(),
                message: "Missing or invalid api token".to_string(),
                path: None,
            }],
        });
    }

    let key: Hmac<Sha256> = Hmac::new_from_slice(CONFIG.jwt_signing_secret.as_bytes()).unwrap();


@@ 46,26 44,22 @@ pub async fn beam_out(data: Json<BeamoutRequest>, state: Data<AppState>) -> Http
        Err(e) => {
            error!("verifying error: {}", e);
            return HttpResponse::Unauthorized().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_BAD_TOKEN".to_string(),
                        message: "Missing or invalid user token".to_string(),
                        path: None,
                    }
                ],
            })
                errors: vec![APIError {
                    code: "ERR_BAD_TOKEN".to_string(),
                    message: "Missing or invalid user token".to_string(),
                    path: None,
                }],
            });
        }
    };

    if !token.contains_key("user") || !token.contains_key("nonce") {
        return HttpResponse::Unauthorized().json(APIErrorsResponse {
            errors: vec![
                APIError {
                    code: "ERR_BAD_TOKEN".to_string(),
                    message: "Missing or invalid user token (missing scopes)".to_string(),
                    path: None,
                }
            ],
            errors: vec![APIError {
                code: "ERR_BAD_TOKEN".to_string(),
                message: "Missing or invalid user token (missing scopes)".to_string(),
                path: None,
            }],
        });
    }



@@ 74,7 68,10 @@ pub async fn beam_out(data: Json<BeamoutRequest>, state: Data<AppState>) -> Http
        id: format!("save-{}", Ulid::new().to_string()),
        user: token.get("user").unwrap().clone(),
        data: saved_data,
        timestamp: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64,
        timestamp: SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64,
    };
    let savefile_active_model = savefile_model.into_active_model();
    match savefile_active_model.insert(&state.conn).await {


@@ 82,16 79,14 @@ pub async fn beam_out(data: Json<BeamoutRequest>, state: Data<AppState>) -> Http
        Err(e) => {
            error!("database error: {}", e);
            return HttpResponse::InternalServerError().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_DB_ERROR".to_string(),
                        message: "database failure".to_string(),
                        path: None,
                    }
                ],
                errors: vec![APIError {
                    code: "ERR_DB_ERROR".to_string(),
                    message: "database failure".to_string(),
                    path: None,
                }],
            });
        }
    }

    HttpResponse::Ok().json(BeamoutResponse {})
}
\ No newline at end of file
}

M api/src/routes/callback.rs => api/src/routes/callback.rs +72 -63
@@ 1,7 1,8 @@
use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};
use actix_web::{get, HttpResponse};
use crate::config::CONFIG;
use crate::error::{APIError, APIErrorsResponse};
use crate::AppState;
use actix_web::web::{Data, Query};
use actix_web::{get, HttpResponse};
use hmac::digest::KeyInit;
use hmac::Hmac;
use jwt::{PKeyWithDigest, SignWithKey, VerifyWithKey};


@@ 11,16 12,15 @@ use openssl::pkey::PKey;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter};
use serde::Deserialize;
use sha2::Sha256;
use ulid::Ulid;
use starkingdoms_api_entities::entity;
use crate::AppState;
use crate::config::CONFIG;
use crate::error::{APIError, APIErrorsResponse};
use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};
use ulid::Ulid;

#[derive(Deserialize)]
pub struct CallbackQueryParams {
    pub token: String,
    pub realm: String
    pub realm: String,
}

#[get("/callback")]


@@ 33,20 33,18 @@ pub async fn callback(query: Query<CallbackQueryParams>, state: Data<AppState>) 
        None => {
            error!("realm not found");
            return HttpResponse::Unauthorized().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_UNKNOWN_REALM".to_string(),
                        message: "Unknown realm".to_string(),
                        path: None,
                    }
                ],
            })
                errors: vec![APIError {
                    code: "ERR_UNKNOWN_REALM".to_string(),
                    message: "Unknown realm".to_string(),
                    path: None,
                }],
            });
        }
    };

    let rs256_pub_key = PKeyWithDigest {
        digest: MessageDigest::sha256(),
        key: PKey::public_key_from_pem(realm.public_key.as_bytes()).unwrap()
        key: PKey::public_key_from_pem(realm.public_key.as_bytes()).unwrap(),
    };

    let token: BTreeMap<String, String> = match query.token.verify_with_key(&rs256_pub_key) {


@@ 59,35 57,35 @@ pub async fn callback(query: Query<CallbackQueryParams>, state: Data<AppState>) 

    let realm = match token.get("realm").ok_or(generic_unauthorized()) {
        Ok(r) => r,
        Err(e) => return e
        Err(e) => return e,
    };
    let realm_local_id = match token.get("realm_native_id").ok_or(generic_unauthorized()) {
        Ok(r) => r,
        Err(e) => return e
        Err(e) => return e,
    };

    debug!("got authenticated realm native authorization: authenticated as {}:{}", realm, realm_local_id);
    debug!(
        "got authenticated realm native authorization: authenticated as {}:{}",
        realm, realm_local_id
    );

    // see if a user on this realm already exists
    let maybe_user = match entity::user_auth_realm::Entity::find()
        .filter(
            entity::user_auth_realm::Column::RealmNativeId.eq(realm_local_id.clone())
        )
        .filter(
            entity::user_auth_realm::Column::Realm.eq(realm.clone())
        ).one(&state.conn).await {
        .filter(entity::user_auth_realm::Column::RealmNativeId.eq(realm_local_id.clone()))
        .filter(entity::user_auth_realm::Column::Realm.eq(realm.clone()))
        .one(&state.conn)
        .await
    {
        Ok(r) => r,
        Err(e) => {
            error!("database error: {}", e);
            return HttpResponse::InternalServerError().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_DB_ERROR".to_string(),
                        message: "Database error".to_string(),
                        path: None,
                    }
                ],
            })
                errors: vec![APIError {
                    code: "ERR_DB_ERROR".to_string(),
                    message: "Database error".to_string(),
                    path: None,
                }],
            });
        }
    };



@@ 98,17 96,27 @@ pub async fn callback(query: Query<CallbackQueryParams>, state: Data<AppState>) 
        claims.insert("nonce", Ulid::new().to_string());
        let token_str = claims.sign_with_key(&key).unwrap();
        let auth_url = format!("{}/?token={}&user={}", CONFIG.game, token_str, user.id);
        return HttpResponse::Found().append_header(("Location", auth_url)).finish();
        return HttpResponse::Found()
            .append_header(("Location", auth_url))
            .finish();
    }

    // create the user
    let new_user = entity::user::Model {
        id: format!("user-{}", Ulid::new().to_string()),
        username: Ulid::new().to_string(),
        created_on: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64,
        created_on: SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64,
    };
    let new_user_realm = entity::user_auth_realm::Model {
        id: format!("{}-{}:{}", new_user.id.clone(), realm.clone(), realm_local_id.clone()),
        id: format!(
            "{}-{}:{}",
            new_user.id.clone(),
            realm.clone(),
            realm_local_id.clone()
        ),
        realm: realm.clone(),
        realm_native_id: realm_local_id.clone(),
        user: new_user.id.clone(),


@@ 119,7 127,12 @@ pub async fn callback(query: Query<CallbackQueryParams>, state: Data<AppState>) 
    claims.insert("user", new_user.id.clone());
    claims.insert("nonce", Ulid::new().to_string());
    let token_str = claims.sign_with_key(&key).unwrap();
    let auth_url = format!("{}/?token={}&user={}", CONFIG.game, token_str, new_user.id.clone());
    let auth_url = format!(
        "{}/?token={}&user={}",
        CONFIG.game,
        token_str,
        new_user.id.clone()
    );

    let new_user_active_model = new_user.into_active_model();
    let new_user_realm_active_model = new_user_realm.into_active_model();


@@ 129,14 142,12 @@ pub async fn callback(query: Query<CallbackQueryParams>, state: Data<AppState>) 
        Err(e) => {
            error!("database error: {}", e);
            return HttpResponse::InternalServerError().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_DB_ERROR".to_string(),
                        message: "Database error".to_string(),
                        path: None,
                    }
                ],
            })
                errors: vec![APIError {
                    code: "ERR_DB_ERROR".to_string(),
                    message: "Database error".to_string(),
                    path: None,
                }],
            });
        }
    }



@@ 145,28 156,26 @@ pub async fn callback(query: Query<CallbackQueryParams>, state: Data<AppState>) 
        Err(e) => {
            error!("database error: {}", e);
            return HttpResponse::InternalServerError().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_DB_ERROR".to_string(),
                        message: "Database error".to_string(),
                        path: None,
                    }
                ],
            })
                errors: vec![APIError {
                    code: "ERR_DB_ERROR".to_string(),
                    message: "Database error".to_string(),
                    path: None,
                }],
            });
        }
    }

    HttpResponse::Found().append_header(("Location", auth_url)).finish()
    HttpResponse::Found()
        .append_header(("Location", auth_url))
        .finish()
}

fn generic_unauthorized() -> HttpResponse {
    HttpResponse::Unauthorized().json(APIErrorsResponse {
        errors: vec![
            APIError {
                code: "ERR_INVALID_STATE".to_string(),
                message: "Unknown/invalid login state".to_string(),
                path: None,
            }
        ],
        errors: vec![APIError {
            code: "ERR_INVALID_STATE".to_string(),
            message: "Unknown/invalid login state".to_string(),
            path: None,
        }],
    })
}
\ No newline at end of file
}

M api/src/routes/mod.rs => api/src/routes/mod.rs +2 -2
@@ 1,4 1,4 @@
pub mod beamin;
pub mod select_realm;
pub mod beamout;
pub mod callback;
pub mod beamout;
\ No newline at end of file
pub mod select_realm;

M api/src/routes/select_realm.rs => api/src/routes/select_realm.rs +21 -23
@@ 1,17 1,17 @@
use std::collections::HashMap;
use crate::config::{StarkingdomsApiConfigRealm, CONFIG};
use crate::error::{APIError, APIErrorsResponse};
use crate::AppState;
use actix_web::web::Data;
use actix_web::{get, HttpResponse};
use actix_web::web::{Data};
use log::error;
use serde::{Serialize};
use serde::Serialize;
use std::collections::HashMap;
use tera::Context;
use crate::AppState;
use crate::config::{CONFIG, StarkingdomsApiConfigRealm};
use crate::error::{APIError, APIErrorsResponse};

#[derive(Serialize)]
pub struct RealmsListTemplateContext {
    pub realms: HashMap<String, StarkingdomsApiConfigRealm>,
    pub back_to: String
    pub back_to: String,
}

#[get("/select-realm")]


@@ 24,14 24,13 @@ pub async fn select_realm(state: Data<AppState>) -> HttpResponse {
        Err(e) => {
            error!("[context] error creating render context: {}", e);
            return HttpResponse::InternalServerError().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_INTERNAL_SERVER_ERROR".to_string(),
                        message: "There was an error processing your request. Please try again later.".to_string(),
                        path: None,
                    }
                ],
            })
                errors: vec![APIError {
                    code: "ERR_INTERNAL_SERVER_ERROR".to_string(),
                    message: "There was an error processing your request. Please try again later."
                        .to_string(),
                    path: None,
                }],
            });
        }
    };
    match state.templates.render("select_realm.tera", &context) {


@@ 39,14 38,13 @@ pub async fn select_realm(state: Data<AppState>) -> HttpResponse {
        Err(e) => {
            error!("[context] error creating render context: {}", e);
            HttpResponse::InternalServerError().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_INTERNAL_SERVER_ERROR".to_string(),
                        message: "There was an error processing your request. Please try again later.".to_string(),
                        path: None,
                    }
                ],
                errors: vec![APIError {
                    code: "ERR_INTERNAL_SERVER_ERROR".to_string(),
                    message: "There was an error processing your request. Please try again later."
                        .to_string(),
                    path: None,
                }],
            })
        }
    }
}
\ No newline at end of file
}

M api/starkingdoms_api_entities/src/lib.rs => api/starkingdoms_api_entities/src/lib.rs +1 -1
@@ 1,1 1,1 @@
pub mod entity;
\ No newline at end of file
pub mod entity;

M api/starkingdoms_api_migration/src/m20230417_162824_create_table_users.rs => api/starkingdoms_api_migration/src/m20230417_162824_create_table_users.rs +16 -9
@@ 6,14 6,21 @@ pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager.create_table(
            Table::create()
                .table(User::Table)
                .col(ColumnDef::new(User::Id).string().primary_key().not_null())
                .col(ColumnDef::new(User::Username).string().unique_key().not_null())
                .col(ColumnDef::new(User::CreatedOn).big_unsigned().not_null())
                .to_owned()
        ).await
        manager
            .create_table(
                Table::create()
                    .table(User::Table)
                    .col(ColumnDef::new(User::Id).string().primary_key().not_null())
                    .col(
                        ColumnDef::new(User::Username)
                            .string()
                            .unique_key()
                            .not_null(),
                    )
                    .col(ColumnDef::new(User::CreatedOn).big_unsigned().not_null())
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {


@@ 29,5 36,5 @@ pub enum User {
    Table,
    Id,
    Username,
    CreatedOn
    CreatedOn,
}

M api/starkingdoms_api_migration/src/m20230417_164240_create_table_user_auth_realms.rs => api/starkingdoms_api_migration/src/m20230417_164240_create_table_user_auth_realms.rs +27 -15
@@ 1,5 1,5 @@
use sea_orm_migration::prelude::*;
use crate::m20230417_162824_create_table_users::User;
use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;


@@ 7,19 7,31 @@ pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager.create_table(
            Table::create()
                .table(UserAuthRealm::Table)
                .col(ColumnDef::new(UserAuthRealm::Id).string().not_null().primary_key())
                .col(ColumnDef::new(UserAuthRealm::Realm).string().not_null())
                .col(ColumnDef::new(UserAuthRealm::RealmNativeId).string().not_null())
                .col(ColumnDef::new(UserAuthRealm::User).string().not_null())
                .foreign_key(
                    ForeignKey::create()
                        .from(UserAuthRealm::Table, UserAuthRealm::User)
                        .to(User::Table, User::Id)
                ).to_owned()
        ).await
        manager
            .create_table(
                Table::create()
                    .table(UserAuthRealm::Table)
                    .col(
                        ColumnDef::new(UserAuthRealm::Id)
                            .string()
                            .not_null()
                            .primary_key(),
                    )
                    .col(ColumnDef::new(UserAuthRealm::Realm).string().not_null())
                    .col(
                        ColumnDef::new(UserAuthRealm::RealmNativeId)
                            .string()
                            .not_null(),
                    )
                    .col(ColumnDef::new(UserAuthRealm::User).string().not_null())
                    .foreign_key(
                        ForeignKey::create()
                            .from(UserAuthRealm::Table, UserAuthRealm::User)
                            .to(User::Table, User::Id),
                    )
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {


@@ 36,5 48,5 @@ pub enum UserAuthRealm {
    Id,
    Realm,
    RealmNativeId,
    User
    User,
}

M api/starkingdoms_api_migration/src/m20230420_144333_create_table_user_data.rs => api/starkingdoms_api_migration/src/m20230420_144333_create_table_user_data.rs +28 -16
@@ 1,5 1,5 @@
use sea_orm_migration::prelude::*;
use crate::m20230417_164240_create_table_user_auth_realms::UserAuthRealm;
use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;


@@ 7,20 7,32 @@ pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager.create_table(
            Table::create()
                .table(UserSavefile::Table)
                .col(ColumnDef::new(UserSavefile::Id).string().not_null().primary_key())
                .col(ColumnDef::new(UserSavefile::User).string().not_null())
                .col(ColumnDef::new(UserSavefile::Data).string().not_null())
                .col(ColumnDef::new(UserSavefile::Timestamp).big_unsigned().not_null().unique_key())
                .foreign_key(
                    ForeignKey::create()
                        .from(UserSavefile::Table, UserSavefile::User)
                        .to(UserAuthRealm::Table, UserAuthRealm::Id)
                )
                .to_owned()
        ).await
        manager
            .create_table(
                Table::create()
                    .table(UserSavefile::Table)
                    .col(
                        ColumnDef::new(UserSavefile::Id)
                            .string()
                            .not_null()
                            .primary_key(),
                    )
                    .col(ColumnDef::new(UserSavefile::User).string().not_null())
                    .col(ColumnDef::new(UserSavefile::Data).string().not_null())
                    .col(
                        ColumnDef::new(UserSavefile::Timestamp)
                            .big_unsigned()
                            .not_null()
                            .unique_key(),
                    )
                    .foreign_key(
                        ForeignKey::create()
                            .from(UserSavefile::Table, UserSavefile::User)
                            .to(UserAuthRealm::Table, UserAuthRealm::Id),
                    )
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {


@@ 37,5 49,5 @@ pub enum UserSavefile {
    Id,
    User,
    Data,
    Timestamp
    Timestamp,
}

M protocol/src/api.rs => protocol/src/api.rs +1 -3
@@ 2,6 2,4 @@ use serde::{Deserialize, Serialize};

// ALL FIELDS **MUST** BE WRAPPED IN Option<>
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
pub struct APISavedPlayerData {

}
\ No newline at end of file
pub struct APISavedPlayerData {}

M protocol/src/legacy.rs => protocol/src/legacy.rs +14 -15
@@ 5,7 5,7 @@ pub const PROTOCOL_VERSION: u32 = 1;
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum State {
    Handshake,
    Play
    Play,
}

#[derive(Serialize, Deserialize, Debug, Clone)]


@@ 13,15 13,15 @@ pub enum MessageC2S {
    Hello {
        version: u32,
        requested_username: String,
        next_state: State
        next_state: State,
    },

    Goodbye {
        reason: GoodbyeReason
        reason: GoodbyeReason,
    },

    Chat {
        message: String
        message: String,
    },

    Ping {},


@@ 32,28 32,27 @@ pub enum MessageS2C {
    Hello {
        version: u32,
        given_username: String,
        next_state: State
        next_state: State,
    },

    Goodbye {
        reason: GoodbyeReason
        reason: GoodbyeReason,
    },

    Chat {
        from: String,
        message: String
        message: String,
    },

    Pong {},

    PlayersUpdate {
        players: Vec<ProtocolPlayer>
        players: Vec<ProtocolPlayer>,
    },


    PlanetData {
        planets: Vec<ProtocolPlanet>
    }
        planets: Vec<ProtocolPlanet>,
    },
}

#[derive(Serialize, Deserialize, Debug, Clone)]


@@ 61,7 60,7 @@ pub struct ProtocolPlayer {
    pub rotation: f64,
    pub x: f64,
    pub y: f64,
    pub username: String
    pub username: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]


@@ 79,18 78,18 @@ pub struct ProtocolPlanet {
    pub planet_type: PlanetType,
    pub x: f64,
    pub y: f64,
    pub radius: f64
    pub radius: f64,
}

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

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

M protocol/src/lib.rs => protocol/src/lib.rs +112 -71
@@ 1,9 1,15 @@
use std::error::Error;
use protobuf::{Enum, Message};
use crate::message_c2s::{MessageC2SAuthenticateAndBeamOut, MessageC2SChat, MessageC2SGoodbye, MessageC2SHello, MessageC2SInput, MessageC2SMouseInput, MessageC2SPing};
use crate::message_s2c::{MessageS2CChat, MessageS2CGoodbye, MessageS2CHello, MessageS2CPlanetData, MessageS2CPlayersUpdate, MessageS2CPong, MessageS2CModulesUpdate};
use crate::message_c2s::{
    MessageC2SAuthenticateAndBeamOut, MessageC2SChat, MessageC2SGoodbye, MessageC2SHello,
    MessageC2SInput, MessageC2SMouseInput, MessageC2SPing,
};
use crate::message_s2c::{
    MessageS2CChat, MessageS2CGoodbye, MessageS2CHello, MessageS2CModulesUpdate,
    MessageS2CPlanetData, MessageS2CPlayersUpdate, MessageS2CPong,
};
use crate::planet::PlanetType;
use crate::starkingdoms_protocol::PacketWrapper;
use protobuf::{Enum, Message};
use std::error::Error;
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));

pub const PROTOCOL_VERSION: u32 = 4;


@@ 18,7 24,7 @@ pub enum MessageC2S {
    Ping(MessageC2SPing),
    Input(MessageC2SInput),
    AuthenticateAndBeamOut(MessageC2SAuthenticateAndBeamOut),
    MouseInput(MessageC2SMouseInput)
    MouseInput(MessageC2SMouseInput),
}

#[derive(Debug)]


@@ 41,26 47,35 @@ impl TryFrom<&[u8]> for MessageC2S {
        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)?)
            },
            }
            _id if _id == message_c2s::message_c2sinput::Packet_info::type_.value() as i64 => {
                MessageC2S::Input(MessageC2SInput::parse_from_bytes(&pkt.packet_data)?)
            },
            _id if _id == message_c2s::message_c2sauthenticate_and_beam_out::Packet_info::type_.value() as i64 => {
                MessageC2S::AuthenticateAndBeamOut(MessageC2SAuthenticateAndBeamOut::parse_from_bytes(&pkt.packet_data)?)
            },
            _id if _id == message_c2s::message_c2smouse_input::Packet_info::type_.value() as i64 => {
            }
            _id if _id
                == message_c2s::message_c2sauthenticate_and_beam_out::Packet_info::type_.value()
                    as i64 =>
            {
                MessageC2S::AuthenticateAndBeamOut(
                    MessageC2SAuthenticateAndBeamOut::parse_from_bytes(&pkt.packet_data)?,
                )
            }
            _id if _id
                == message_c2s::message_c2smouse_input::Packet_info::type_.value() as i64 =>
            {
                MessageC2S::MouseInput(MessageC2SMouseInput::parse_from_bytes(&pkt.packet_data)?)
            }
            _id => { return Err(format!("Unrecognized C2S packet {}", _id).into()); }
            _id => {
                return Err(format!("Unrecognized C2S packet {}", _id).into());
            }
        };

        Ok(deser_pkt)


@@ 72,27 87,34 @@ impl TryInto<Vec<u8>> for MessageC2S {

    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()?)
            }
            MessageC2S::Input(p) => {
                (message_c2s::message_c2sping::Packet_info::type_.value(), p.write_to_bytes()?)
            },
            MessageC2S::AuthenticateAndBeamOut(p) => {
                (message_c2s::message_c2sauthenticate_and_beam_out::Packet_info::type_.value(), p.write_to_bytes()?)
            },
            MessageC2S::MouseInput(p) => {
                (message_c2s::message_c2smouse_input::Packet_info::type_.value(), p.write_to_bytes()?)
            }
            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()?,
            ),
            MessageC2S::Input(p) => (
                message_c2s::message_c2sping::Packet_info::type_.value(),
                p.write_to_bytes()?,
            ),
            MessageC2S::AuthenticateAndBeamOut(p) => (
                message_c2s::message_c2sauthenticate_and_beam_out::Packet_info::type_.value(),
                p.write_to_bytes()?,
            ),
            MessageC2S::MouseInput(p) => (
                message_c2s::message_c2smouse_input::Packet_info::type_.value(),
                p.write_to_bytes()?,
            ),
        };

        let pkt = PacketWrapper {


@@ 114,26 136,38 @@ impl TryFrom<&[u8]> for MessageS2C {
        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 => {
            }
            _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)?)
            },
            _id if _id == message_s2c::message_s2cmodules_update::Packet_info::type_.value() as i64 => {
                MessageS2C::ModulesUpdate(MessageS2CModulesUpdate::parse_from_bytes(&pkt.packet_data)?)
            },
            _ => { return Err("Not a S2C packet".into()); }
            }
            _id if _id
                == message_s2c::message_s2cmodules_update::Packet_info::type_.value() as i64 =>
            {
                MessageS2C::ModulesUpdate(MessageS2CModulesUpdate::parse_from_bytes(
                    &pkt.packet_data,
                )?)
            }
            _ => {
                return Err("Not a S2C packet".into());
            }
        };

        Ok(deser_pkt)


@@ 145,27 179,34 @@ impl TryInto<Vec<u8>> for MessageS2C {

    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()?)
            }
            MessageS2C::ModulesUpdate(p) => {
                (message_s2c::message_s2cmodules_update::Packet_info::type_.value(), p.write_to_bytes()?)
            }
            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()?,
            ),
            MessageS2C::ModulesUpdate(p) => (
                message_s2c::message_s2cmodules_update::Packet_info::type_.value(),
                p.write_to_bytes()?,
            ),
        };

        let pkt = PacketWrapper {


@@ 183,7 224,7 @@ impl planet::PlanetType {
        match self {
            PlanetType::Earth => "earth".to_string(),
            PlanetType::Moon => "moon".to_string(),
            PlanetType::UNKNOWN => "missing".to_string()
            PlanetType::UNKNOWN => "missing".to_string(),
        }
    }
}

M server/build.rs => server/build.rs +19 -6
@@ 1,5 1,5 @@
use std::process::Command;
use cargo_metadata::MetadataCommand;
use std::process::Command;

fn main() {
    let path = std::env::var("CARGO_MANIFEST_DIR").unwrap();


@@ 14,16 14,29 @@ fn main() {

    let version = root.version.to_string();
    let version_name = root.metadata["version-name"].to_string().replace('"', "");
    let description = root.metadata["slp-description"].to_string().replace('"', "");
    let description = root.metadata["slp-description"]
        .to_string()
        .replace('"', "");

    let output = Command::new("git").args(["rev-parse", "--short", "HEAD"]).output().unwrap();
    let output = Command::new("git")
        .args(["rev-parse", "--short", "HEAD"])
        .output()
        .unwrap();
    let git_hash = String::from_utf8(output.stdout).unwrap();

    println!("cargo:rustc-env=STK_VERSION={}", version);
    println!("cargo:rustc-env=STK_VERSION_NAME={}", version_name);
    println!("cargo:rustc-env=STK_SLP_DESCRIPTION={}", description);
    println!("cargo:rustc-env=STK_CHANNEL={}", std::env::var("STK_CHANNEL").unwrap_or("dev".to_string()));
    println!("cargo:rustc-env=STK_BUILD={}-{}-{}", std::env::var("STK_CHANNEL").unwrap_or("dev".to_string()), std::env::var("STK_BUILD_NUM").unwrap_or("local".to_string()), git_hash);
    println!(
        "cargo:rustc-env=STK_CHANNEL={}",
        std::env::var("STK_CHANNEL").unwrap_or("dev".to_string())
    );
    println!(
        "cargo:rustc-env=STK_BUILD={}-{}-{}",
        std::env::var("STK_CHANNEL").unwrap_or("dev".to_string()),
        std::env::var("STK_BUILD_NUM").unwrap_or("local".to_string()),
        git_hash
    );
    println!("cargo:rerun-if-changed=Cargo.toml");
    println!("cargo:rerun-if-env-changed=STK_BUILD_NUM");
}
\ No newline at end of file
}

M server/src/api.rs => server/src/api.rs +44 -17
@@ 1,40 1,53 @@
use std::error::Error;
use log::error;
use reqwest::StatusCode;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
use starkingdoms_protocol::api::APISavedPlayerData;
use std::error::Error;

#[derive(Serialize, Deserialize)]
pub struct BeaminRequest {
    pub api_token: String,
    pub user_auth_realm_id: String,
    pub user_auth_token: String
    pub user_auth_token: String,
}

#[derive(Serialize, Deserialize)]
pub struct BeaminResponse {
    pub save_id: String,
    pub save: APISavedPlayerData
    pub save: APISavedPlayerData,
}

pub async fn load_player_data_from_api(token: &str, user_id: &str, internal_token: &str) -> Result<APISavedPlayerData, Box<dyn Error>> {
pub async fn load_player_data_from_api(
    token: &str,
    user_id: &str,
    internal_token: &str,
) -> Result<APISavedPlayerData, Box<dyn Error>> {
    let client = reqwest::Client::new();

    let req_body = BeaminRequest {
        api_token: internal_token.to_owned(),
        user_auth_realm_id: user_id.to_owned(),
        user_auth_token: token.to_owned()
        user_auth_token: token.to_owned(),
    };

    let res = client.post(format!("{}/beamin", std::env::var("STK_API_URL").unwrap())).header("Content-Type", "application/json").body(serde_json::to_string(&req_body)?).send().await?;
    let res = client
        .post(format!("{}/beamin", std::env::var("STK_API_URL").unwrap()))
        .header("Content-Type", "application/json")
        .body(serde_json::to_string(&req_body)?)
        .send()
        .await?;

    if res.status() == StatusCode::NO_CONTENT {
        return Ok(APISavedPlayerData::default())
        return Ok(APISavedPlayerData::default());
    }

    if res.status() != StatusCode::OK {
        error!("error with API call (status: {}, body: {})", res.status(), res.text().await?);
        return Err("Error with API call".into())
        error!(
            "error with API call (status: {}, body: {})",
            res.status(),
            res.text().await?
        );
        return Err("Error with API call".into());
    }

    let resp: BeaminResponse = serde_json::from_str(&res.text().await?)?;


@@ 47,25 60,39 @@ pub struct BeamoutRequest {
    pub api_token: String,
    pub user_auth_realm_id: String,
    pub user_auth_token: String,
    pub data: APISavedPlayerData
    pub data: APISavedPlayerData,
}

pub async fn save_player_data_to_api(data: &APISavedPlayerData, token: &str, user_id: &str, internal_token: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
pub async fn save_player_data_to_api(
    data: &APISavedPlayerData,
    token: &str,
    user_id: &str,
    internal_token: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
    let client = reqwest::Client::new();

    let req_body = BeamoutRequest {
        api_token: internal_token.to_owned(),
        user_auth_realm_id: user_id.to_owned(),
        user_auth_token: token.to_owned(),
        data: data.to_owned()
        data: data.to_owned(),
    };

    let res = client.post(format!("{}/beamout", std::env::var("STK_API_URL").unwrap())).header("Content-Type", "application/json").body(serde_json::to_string(&req_body)?).send().await?;
    let res = client
        .post(format!("{}/beamout", std::env::var("STK_API_URL").unwrap()))
        .header("Content-Type", "application/json")
        .body(serde_json::to_string(&req_body)?)
        .send()
        .await?;

    if res.status() != StatusCode::OK {
        error!("error with API call (status: {}, body: {})", res.status(), res.text().await?);
        return Err("Error with API call".into())
        error!(
            "error with API call (status: {}, body: {})",
            res.status(),
            res.text().await?
        );
        return Err("Error with API call".into());
    }

    Ok(())
}
\ No newline at end of file
}

M server/src/entity.rs => server/src/entity.rs +11 -7
@@ 1,9 1,13 @@
use std::{sync::atomic::AtomicU32, collections::HashMap, net::SocketAddr};
use std::{collections::HashMap, net::SocketAddr, sync::atomic::AtomicU32};

use nalgebra::Vector2;
use starkingdoms_protocol::planet::PlanetType;

use crate::{planet::Planet, SCALE, manager::{ClientHandlerMessage, Player, Module, AttachedModule}};
use crate::{
    manager::{AttachedModule, ClientHandlerMessage, Module, Player},
    planet::Planet,
    SCALE,
};

pub type EntityId = u32;
pub type Entities = HashMap<EntityId, Entity>;


@@ 11,7 15,9 @@ static mut ENTITY_ID_COUNT: AtomicU32 = AtomicU32::new(0);
pub fn get_entity_id() -> EntityId {
    let last_entity_id = unsafe { &ENTITY_ID_COUNT };
    let id = last_entity_id.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
    if id > 4_147_483_600 { panic!("No remaining entity ids") };
    if id > 4_147_483_600 {
        panic!("No remaining entity ids")
    };
    id
}



@@ 23,7 29,7 @@ pub struct EntityHandler {
impl EntityHandler {
    pub fn new() -> EntityHandler {
        EntityHandler {
            entities: Entities::new()
            entities: Entities::new(),
        }
    }
    pub fn get_planets(&self) -> Vec<Planet> {


@@ 160,9 166,7 @@ impl EntityHandler {
            });
        }

        ClientHandlerMessage::PlanetData {
            planets
        }
        ClientHandlerMessage::PlanetData { planets }
    }
}


M server/src/handler.rs => server/src/handler.rs +240 -127
@@ 1,32 1,44 @@
use crate::api::{load_player_data_from_api, save_player_data_to_api};
use crate::entity::{get_entity_id, Entity, EntityHandler};
use crate::manager::{
    AttachedModule, ClientHandlerMessage, ClientManager, ModuleTemplate, PhysicsData, Player,
};
use crate::{recv, send, SCALE};
use async_std::net::TcpStream;
use async_std::{channel::Receiver, sync::RwLock};
use async_tungstenite::WebSocketStream;
use futures::stream::{SplitSink, SplitStream};
use futures::{FutureExt, SinkExt, StreamExt};
use log::{debug, error, info, warn};
use nalgebra::{point, vector};
use rand::Rng;
use rapier2d_f64::prelude::{
    Collider, ColliderBuilder, MassProperties, RigidBodyBuilder, RigidBodyType,
};
use starkingdoms_protocol::goodbye_reason::GoodbyeReason;
use starkingdoms_protocol::message_s2c::{
    MessageS2CChat, MessageS2CGoodbye, MessageS2CHello, MessageS2CModulesUpdate,
    MessageS2CPlanetData, MessageS2CPlayersUpdate, MessageS2CPong,
};
use starkingdoms_protocol::module::ModuleType;
use starkingdoms_protocol::state::State;
use starkingdoms_protocol::{MessageC2S, MessageS2C, PROTOCOL_VERSION};
use std::error::Error;
use std::f64::consts::PI;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use futures::stream::{SplitSink, SplitStream};
use futures::{FutureExt, SinkExt, StreamExt};
use log::{error, info, debug, warn};
use nalgebra::{vector, point};
use rand::Rng;
use rapier2d_f64::prelude::{RigidBodyBuilder, RigidBodyType, ColliderBuilder, MassProperties, Collider};
use starkingdoms_protocol::module::ModuleType;
use tungstenite::Message;
use starkingdoms_protocol::goodbye_reason::GoodbyeReason;
use starkingdoms_protocol::message_s2c::{MessageS2CChat, MessageS2CGoodbye, MessageS2CHello, MessageS2CPlanetData, MessageS2CPlayersUpdate, MessageS2CPong, MessageS2CModulesUpdate};
use starkingdoms_protocol::{MessageS2C, MessageC2S, PROTOCOL_VERSION};
use starkingdoms_protocol::state::State;
use crate::entity::{EntityHandler, get_entity_id, Entity};
use crate::manager::{ClientHandlerMessage, ClientManager, PhysicsData, Player, AttachedModule, ModuleTemplate};
use crate::{send, recv, SCALE};
use async_std::{sync::RwLock, channel::Receiver};
use async_std::net::TcpStream;
use async_tungstenite::WebSocketStream;
use crate::api::{load_player_data_from_api, save_player_data_to_api};

pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandler>>, data: Arc<RwLock<PhysicsData>>,
                           remote_addr: SocketAddr, rx: Receiver<ClientHandlerMessage>,
                           mut client_tx: SplitSink<WebSocketStream<TcpStream>, Message>, mut client_rx: SplitStream<WebSocketStream<TcpStream>>
                          ) -> Result<(), Box<dyn Error>> {
pub async fn handle_client(
    mgr: ClientManager,
    entities: Arc<RwLock<EntityHandler>>,
    data: Arc<RwLock<PhysicsData>>,
    remote_addr: SocketAddr,
    rx: Receiver<ClientHandlerMessage>,
    mut client_tx: SplitSink<WebSocketStream<TcpStream>, Message>,
    mut client_rx: SplitStream<WebSocketStream<TcpStream>>,
) -> Result<(), Box<dyn Error>> {
    let mut state = State::Handshake;
    let mut username = String::new();
    let mut ping_timeout = SystemTime::now() + Duration::from_secs(10);


@@ 41,7 53,8 @@ pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandle
                            from,
                            message,
                            special_fields: Default::default(),
                        }).try_into()?;
                        })
                        .try_into()?;
                        send!(client_tx, msg).await?;
                    }
                }


@@ 50,7 63,8 @@ pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandle
                        let msg = MessageS2C::PlayersUpdate(MessageS2CPlayersUpdate {
                            players,
                            special_fields: Default::default(),
                        }).try_into()?;
                        })
                        .try_into()?;
                        send!(client_tx, msg).await?;
                    }
                }


@@ 59,7 73,8 @@ pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandle
                        let msg = MessageS2C::PlanetData(MessageS2CPlanetData {
                            planets,
                            special_fields: Default::default(),
                        }).try_into()?;
                        })
                        .try_into()?;
                        send!(client_tx, msg).await?;
                    }
                }


@@ 68,7 83,8 @@ pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandle
                        let msg = MessageS2C::ModulesUpdate(MessageS2CModulesUpdate {
                            modules,
                            special_fields: Default::default(),
                        }).try_into()?;
                        })
                        .try_into()?;
                        send!(client_tx, msg).await?;
                    }
                }


@@ 83,7 99,8 @@ pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandle
            let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                reason: GoodbyeReason::PingPongTimeout.into(),
                special_fields: Default::default(),
            }).try_into()?;
            })
            .try_into()?;
            send!(client_tx, msg).await?;
            break;
        }


@@ 96,34 113,52 @@ pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandle
                        MessageC2S::Hello(pkt) => {
                            info!("client sent hello");
                            if !matches!(pkt.next_state.unwrap(), State::Play) {
                                error!("client sent unexpected state {:?} (expected: Play)", pkt.next_state);
                                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()?;
                                })
                                .try_into()?;
                                send!(client_tx, msg).await?;
                                break;
                            }

                            // check version
                            if pkt.version != PROTOCOL_VERSION {
                                error!("client sent incompatible version {} (expected: {})", 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()?;
                                })
                                .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 == pkt.requested_username) {
                                    error!("client requested username {} but it is in use", pkt.requested_username);
                                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()?;
                                    })
                                    .try_into()?;
                                    send!(client_tx, msg).await?;
                                    break;
                                }


@@ 131,7 166,10 @@ pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandle

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

                            let msg = MessageS2C::Hello(MessageS2CHello {


@@ 139,7 177,8 @@ pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandle
                                given_username: pkt.requested_username.clone(),
                                special_fields: Default::default(),
                                next_state: pkt.next_state,
                            }).try_into()?;
                            })
                            .try_into()?;

                            send!(client_tx, msg).await?;



@@ 158,16 197,27 @@ pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandle
                                    rng.gen::<f64>() * PI * 2.
                                };
                                let player_body = RigidBodyBuilder::new(RigidBodyType::Dynamic)
                                    .translation(vector![angle.cos() * 2050. / SCALE, angle.sin() * 2050.0/SCALE])
                                    .translation(vector![
                                        angle.cos() * 2050. / SCALE,
                                        angle.sin() * 2050.0 / SCALE
                                    ])
                                    .rotation(angle + PI / 2.)
                                    .build();
                                let player_collider: Collider = ColliderBuilder::cuboid(25.0 / SCALE, 25.0 / SCALE)
                                    .mass_properties(MassProperties::new(point![0.0, 0.0], 120.0, 122500.0))
                                    .build();
                                let player_collider: Collider =
                                    ColliderBuilder::cuboid(25.0 / SCALE, 25.0 / SCALE)
                                        .mass_properties(MassProperties::new(
                                            point![0.0, 0.0],
                                            120.0,
                                            122500.0,
                                        ))
                                        .build();
                                let player_handle = rigid_body_set.insert(player_body);

                                collider_set.insert_with_parent(player_collider, player_handle, &mut rigid_body_set);

                                collider_set.insert_with_parent(
                                    player_collider,
                                    player_handle,
                                    &mut rigid_body_set,
                                );

                                let mut player = Player {
                                    handle: player_handle,


@@ 183,128 233,191 @@ pub async fn handle_client(mgr: ClientManager, entities: Arc<RwLock<EntityHandle
                                if !pkt.user.is_empty() && !pkt.token.is_empty() {
                                    player.auth_token = Some(pkt.token.clone());
                                    player.auth_user = Some(pkt.user.clone());
                                    info!("[{}] * Beamin: beaming in {} as {} with token {}", remote_addr, username, pkt.user, pkt.token);
                                    info!(
                                        "[{}] * Beamin: beaming in {} as {} with token {}",
                                        remote_addr, username, pkt.user, pkt.token
                                    );

                                    let player_data = match load_player_data_from_api(&pkt.token, &pkt.user, &std::env::var("STK_API_KEY").unwrap()).await {
                                    let player_data = match load_player_data_from_api(
                                        &pkt.token,
                                        &pkt.user,
                                        &std::env::var("STK_API_KEY").unwrap(),
                                    )
                                    .await
                                    {
                                        Ok(d) => d,
                                        Err(e) => {
                                            warn!("[{}] * Beamin: ABORTED. API returned error: {}", remote_addr, e);
                                            e_write_handle.entities.insert(get_entity_id(), Entity::Player(player));
                                            warn!(
                                                "[{}] * Beamin: ABORTED. API returned error: {}",
                                                remote_addr, e
                                            );
                                            e_write_handle
                                                .entities
                                                .insert(get_entity_id(), Entity::Player(player));
                                            continue;
                                        }
                                    };

                                    info!("[{}] Beamin: loaded player data! {:?}", remote_addr, player_data);
                                    info!(
                                        "[{}] Beamin: loaded player data! {:?}",
                                        remote_addr, player_data
                                    );

                                    player.load_api_data(&player_data);
                                }

                                let player_id = get_entity_id();
                                e_write_handle.entities.insert(player_id, Entity::Player(player));
                                AttachedModule::attach_new(&mut data_handle, &mut e_write_handle,
                                                           player_id, player_id,
                                                           ModuleTemplate {
                                                               translation: vector![0.0, 50.0],
                                                               heading: 0.0,
                                                               mass_properties: MassProperties::new(point![0.0, 0.0], 120.0, 122500.0),
                                                               module_type: ModuleType::Cargo,
                                                           }, 0, angle);
                                e_write_handle
                                    .entities
                                    .insert(player_id, Entity::Player(player));
                                AttachedModule::attach_new(
                                    &mut data_handle,
                                    &mut e_write_handle,
                                    player_id,
                                    player_id,
                                    ModuleTemplate {
                                        translation: vector![0.0, 50.0],
                                        heading: 0.0,
                                        mass_properties: MassProperties::new(
                                            point![0.0, 0.0],
                                            120.0,
                                            122500.0,
                                        ),
                                        module_type: ModuleType::Cargo,
                                    },
                                    0,
                                    angle,
                                );
                                data_handle.rigid_body_set = rigid_body_set;
                                data_handle.collider_set = collider_set;
                                debug!("running");
                            }
                        },
                        }
                        MessageC2S::Goodbye(pkt) => {
                            info!("client sent goodbye: {:?}", pkt.reason);
                            break;
                        },
                        }
                        _ => {
                            error!("client sent unexpected packet {:?} for state {:?}", pkt, state);
                            error!(
                                "client sent unexpected packet {:?} for state {:?}",
                                pkt, state
                            );
                            let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                                reason: GoodbyeReason::UnexpectedPacket.into(),
                                special_fields: Default::default(),
                            }).try_into()?;
                            })
                            .try_into()?;
                            send!(client_tx, msg).await?;
                            break;
                        }
                    }
                }
                State::Play => {
                    match pkt {
                        MessageC2S::Hello { .. } => {
                            error!("client sent unexpected packet {:?} for state {:?}", pkt, state);
                            let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                                reason: GoodbyeReason::UnexpectedPacket.into(),
                                special_fields: Default::default(),
                            }).try_into()?;
                            send!(client_tx, msg).await?;
                            break;
                        },
                        MessageC2S::Goodbye(pkt) => {
                            info!("client sent goodbye: {:?}", pkt.reason);
                            break;
                        },
                        MessageC2S::Chat(pkt) => {
                            info!("[{}] CHAT: [{}] {}", remote_addr, username, pkt.message);
                State::Play => match pkt {
                    MessageC2S::Hello { .. } => {
                        error!(
                            "client sent unexpected packet {:?} for state {:?}",
                            pkt, state
                        );
                        let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                            reason: GoodbyeReason::UnexpectedPacket.into(),
                            special_fields: Default::default(),
                        })
                        .try_into()?;
                        send!(client_tx, msg).await?;
                        break;
                    }
                    MessageC2S::Goodbye(pkt) => {
                        info!("client sent goodbye: {:?}", pkt.reason);
                        break;
                    }
                    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: pkt.message.clone() }).await {
                                    Ok(_) => (),
                                    Err(e) => {
                                        error!("unable to update a client thread: {}", e);
                                    }
                        for (_addr, client_thread) in mgr.handlers.read().await.iter() {
                            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);
                                }
                            }
                        },
                        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);
                        },
                        MessageC2S::Input(p) => {
                            let mut handle = entities.write().await;
                            let id = handle.get_player_id(remote_addr)
                                .expect("could not get player id");
                            if let Entity::Player(ref mut me) = handle.entities.get_mut(&id)
                                .expect("player disconnected but continued to send packets") {
                                me.input.up = p.up_pressed;
                                me.input.down = p.down_pressed;
                                me.input.left = p.left_pressed;
                                me.input.right = p.right_pressed;
                            }
                        },
                        MessageC2S::AuthenticateAndBeamOut(p) => {
                            info!("[{}] * Beaming out {} as {} with realm token {}", remote_addr, username, p.user_id, p.token);
                        }
                    }
                    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);
                    }
                    MessageC2S::Input(p) => {
                        let mut handle = entities.write().await;
                        let id = handle
                            .get_player_id(remote_addr)
                            .expect("could not get player id");
                        if let Entity::Player(ref mut me) = handle
                            .entities
                            .get_mut(&id)
                            .expect("player disconnected but continued to send packets")
                        {
                            me.input.up = p.up_pressed;
                            me.input.down = p.down_pressed;
                            me.input.left = p.left_pressed;
                            me.input.right = p.right_pressed;
                        }
                    }
                    MessageC2S::AuthenticateAndBeamOut(p) => {
                        info!(
                            "[{}] * Beaming out {} as {} with realm token {}",
                            remote_addr, username, p.user_id, p.token
                        );

                            let player = entities.read().await.get_player(remote_addr).expect("Player sending messages after disconnect");
                        let player = entities
                            .read()
                            .await
                            .get_player(remote_addr)
                            .expect("Player sending messages after disconnect");

                            if Some(p.token) != player.auth_token || Some(p.user_id) != player.auth_user {
                                warn!("[{}] invalid beamout packet, ignoring", remote_addr);
                                continue;
                            }
                        if Some(p.token) != player.auth_token || Some(p.user_id) != player.auth_user
                        {
                            warn!("[{}] invalid beamout packet, ignoring", remote_addr);
                            continue;
                        }

                            match save_player_data_to_api(&player.as_api_data(), &player.auth_token.unwrap(), &player.auth_user.unwrap(), &std::env::var("STK_API_KEY").unwrap()).await {
                                Ok(_) => {
                                    info!("[{}] * Beamed out successfully", remote_addr);
                                    let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                                        reason: GoodbyeReason::Done.into(),
                                        special_fields: Default::default(),
                                    }).try_into()?;
                                    send!(client_tx, msg).await?;
                                    break;
                                }
                                Err(e) => {
                                    error!("[{}] error beaming out: {}", remote_addr, e);
                                }
                        match save_player_data_to_api(
                            &player.as_api_data(),
                            &player.auth_token.unwrap(),
                            &player.auth_user.unwrap(),
                            &std::env::var("STK_API_KEY").unwrap(),
                        )
                        .await
                        {
                            Ok(_) => {
                                info!("[{}] * Beamed out successfully", remote_addr);
                                let msg = MessageS2C::Goodbye(MessageS2CGoodbye {
                                    reason: GoodbyeReason::Done.into(),
                                    special_fields: Default::default(),
                                })
                                .try_into()?;
                                send!(client_tx, msg).await?;
                                break;
                            }
                            Err(e) => {
                                error!("[{}] error beaming out: {}", remote_addr, e);
                            }
                        },
                        MessageC2S::MouseInput(p) => {
                            debug!("[{}] player input: {:?}", remote_addr, p);
                        }
                    }
                }
                    MessageC2S::MouseInput(p) => {
                        debug!("[{}] player input: {:?}", remote_addr, p);
                    }
                },
            }
        }
    }

M server/src/macros.rs => server/src/macros.rs +39 -43
@@ 13,49 13,13 @@ pub fn _generic_pkt_into(p: Vec<u8>) -> Message {

#[macro_export]
macro_rules! recv {
    ($reader:expr) => {
        {
            if let Some(future_result) = $reader.next().now_or_never() {
                if let Some(msg) = future_result {
                    match msg {
                        Ok(msg) => {
                            if msg.is_binary() {
                                match MessageC2S::try_from(msg.into_data().as_slice()) {
                                    Ok(d) => Ok(Some(d)),
                                    Err(e) => {
                                        log::error!("error deserializing message: {}", e);
                                        Ok(None)
                                    }
                                }
                            } else {
                                Ok(None)
                            }
                        },
                        Err(e) => {
                            log::error!("error receiving message: {}", e);
                            Ok(None)
                        }
                    }
                } else {
                    log::error!("pipe closed");
                    Err("Pipe closed")
                }
            } else {
                Ok(None)
            }
        }
    }
}

#[macro_export]
macro_rules! recv_now {
    ($reader:expr) => {
        {
            if let Some(msg) = $reader.next().await {
    ($reader:expr) => {{
        if let Some(future_result) = $reader.next().now_or_never() {
            if let Some(msg) = future_result {
                match msg {
                    Ok(msg) => {
                        if msg.is_binary() {
                            match MessageC2S::try_from(&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);


@@ 65,7 29,7 @@ macro_rules! recv_now {
                        } else {
                            Ok(None)
                        }
                    },
                    }
                    Err(e) => {
                        log::error!("error receiving message: {}", e);
                        Ok(None)


@@ 75,6 39,38 @@ macro_rules! recv_now {
                log::error!("pipe closed");
                Err("Pipe closed")
            }
        } else {
            Ok(None)
        }
    };
}
\ No newline at end of file
    }};
}

#[macro_export]
macro_rules! recv_now {
    ($reader:expr) => {{
        if let Some(msg) = $reader.next().await {
            match msg {
                Ok(msg) => {
                    if msg.is_binary() {
                        match MessageC2S::try_from(&msg.into_data()) {
                            Ok(d) => Ok(Some(d)),
                            Err(e) => {
                                log::error!("error deserializing message: {}", e);
                                Ok(None)
                            }
                        }
                    } else {
                        Ok(None)
                    }
                }
                Err(e) => {
                    log::error!("error receiving message: {}", e);
                    Ok(None)
                }
            }
        } else {
            log::error!("pipe closed");
            Err("Pipe closed")
        }
    }};
}

M server/src/main.rs => server/src/main.rs +92 -39
@@ 1,37 1,45 @@
use std::error::Error;
use std::net::SocketAddr;
use async_std::io::WriteExt;
use async_std::sync::Arc;
use async_std::net::{TcpListener, TcpStream};
use entity::{EntityHandler};
use manager::PhysicsData;
use nalgebra::vector;
use rapier2d_f64::prelude::{MultibodyJointSet, ImpulseJointSet, ColliderSet, RigidBodySet, NarrowPhase, BroadPhase, IslandManager, CCDSolver, IntegrationParameters};
use lazy_static::lazy_static;
use log::{error, info, Level, warn};
use serde::{Deserialize, Serialize};
use crate::entity::Entity;
use crate::handler::handle_client;
use crate::manager::{ClientHandler, ClientManager};
use crate::timer::timer_main;
use async_std::io::WriteExt;
use async_std::net::{TcpListener, TcpStream};
use async_std::sync::Arc;
use async_std::sync::RwLock;
use entity::EntityHandler;
use futures::StreamExt;
use lazy_static::lazy_static;
use log::{error, info, warn, Level};
use manager::PhysicsData;
use nalgebra::vector;
use rapier2d_f64::prelude::{
    BroadPhase, CCDSolver, ColliderSet, ImpulseJointSet, IntegrationParameters, IslandManager,
    MultibodyJointSet, NarrowPhase, RigidBodySet,
};
use serde::{Deserialize, Serialize};
use starkingdoms_protocol::PROTOCOL_VERSION;
use crate::handler::handle_client;
use std::error::Error;
use std::net::SocketAddr;

pub mod handler;
pub mod manager;
pub mod timer;
#[macro_use]
pub mod macros;
pub mod planet;
pub mod orbit;
pub mod entity;
pub mod api;
pub mod entity;
pub mod orbit;
pub mod planet;

const SCALE: f64 = 1.0;

async fn handle_request(conn: TcpStream, remote_addr: SocketAddr, mgr: ClientManager,
                        entities: Arc<RwLock<EntityHandler>>, physics_data: Arc<RwLock<PhysicsData>>) {
async fn handle_request(
    conn: TcpStream,
    remote_addr: SocketAddr,
    mgr: ClientManager,
    entities: Arc<RwLock<EntityHandler>>,
    physics_data: Arc<RwLock<PhysicsData>>,
) {
    match _handle_request(conn, remote_addr, mgr, entities, physics_data).await {
        Ok(_) => (),
        Err(e) => {


@@ 40,8 48,13 @@ async fn handle_request(conn: TcpStream, remote_addr: SocketAddr, mgr: ClientMan
    }
}

async fn _handle_request(mut conn: TcpStream, remote_addr: SocketAddr, mgr: ClientManager,
                         entities: Arc<RwLock<EntityHandler>>, physics_data: Arc<RwLock<PhysicsData>>) -> Result<(), Box<dyn Error>> {
async fn _handle_request(
    mut conn: TcpStream,
    remote_addr: SocketAddr,
    mgr: ClientManager,
    entities: Arc<RwLock<EntityHandler>>,
    physics_data: Arc<RwLock<PhysicsData>>,
) -> Result<(), Box<dyn Error>> {
    let mut peek_buf = [0u8; 9];

    loop {


@@ 56,20 69,29 @@ async fn _handle_request(mut conn: TcpStream, remote_addr: SocketAddr, mgr: Clie
        let ping_resp = serde_json::to_string(&ServerPingResponse {
            version: ServerPingResponseVersion {
                name: env!("STK_VERSION_NAME").to_string(), // Set by build.rs
                number: env!("STK_VERSION").to_string(), // Set by build.rs
                number: env!("STK_VERSION").to_string(),    // Set by build.rs
                protocol: PROTOCOL_VERSION,
                channel: env!("STK_CHANNEL").to_string(),
                build: env!("STK_BUILD").to_string()
                build: env!("STK_BUILD").to_string(),
            },
            players: CMGR.usernames.read().await.len() as u32,
            description: env!("STK_SLP_DESCRIPTION").to_string(),
        }).unwrap();
        })
        .unwrap();

        let resp_str = format!("HTTP/1.0 200 OK\nAccess-Control-Allow-Origin: *\nContent-Length: {}\n\n{}", ping_resp.len(), ping_resp);
        let resp_str = format!(
            "HTTP/1.0 200 OK\nAccess-Control-Allow-Origin: *\nContent-Length: {}\n\n{}",
            ping_resp.len(),
            ping_resp
        );
        let http_resp = resp_str.as_bytes();

        conn.write_all(http_resp).await?;
        info!("[{}] sent ping response (200 OK {} bytes)", remote_addr, ping_resp.len());
        info!(
            "[{}] sent ping response (200 OK {} bytes)",
            remote_addr,
            ping_resp.len()
        );
        return Ok(());
    }
    info!("[{}] incoming websocket connection", remote_addr);


@@ 91,16 113,28 @@ async fn _handle_request(mut conn: TcpStream, remote_addr: SocketAddr, mgr: Clie
    info!("[{}] passing to client handler", remote_addr);

    //forward the stream to the sink to achieve echo
    match handle_client(mgr.clone(), entities.clone(), physics_data.clone(), remote_addr, rx, ws_write, ws_read).await {
    match handle_client(
        mgr.clone(),
        entities.clone(),
        physics_data.clone(),
        remote_addr,
        rx,
        ws_write,
        ws_read,
    )
    .await
    {
        Ok(_) => (),
        Err(e) if e.is::<async_tungstenite::tungstenite::error::Error>() => {
            let e = e.downcast::<async_tungstenite::tungstenite::error::Error>().unwrap();
            let e = e
                .downcast::<async_tungstenite::tungstenite::error::Error>()
                .unwrap();
            if matches!(*e, async_tungstenite::tungstenite::Error::ConnectionClosed) {
                info!("[{}] connection closed normally", remote_addr);
            } else {
                error!("[{}] error in client thread: {}", remote_addr, e);
            }
        },
        }
        Err(e) => {
            error!("[{}] error in client thread: {}", remote_addr, e);
        }


@@ 125,8 159,14 @@ async fn _handle_request(mut conn: TcpStream, remote_addr: SocketAddr, mgr: Clie
            }
        };
        if let Entity::Player(player) = entities.read().await.entities.get(&player_id).unwrap() {
            rigid_body_set.remove(player.handle, &mut island_manager, &mut collider_set,
                                    &mut impulse_joint_set, &mut multibody_joint_set, true);
            rigid_body_set.remove(
                player.handle,
                &mut island_manager,
                &mut collider_set,
                &mut impulse_joint_set,
                &mut multibody_joint_set,
                true,
            );
        }
        data.rigid_body_set = rigid_body_set;
        data.collider_set = collider_set;


@@ 144,11 184,12 @@ lazy_static! {
        handlers: Arc::new(RwLock::new(Default::default())),
        usernames: Arc::new(RwLock::new(Default::default())),
    };
    static ref DATA: Arc<RwLock<PhysicsData>> = Arc::new(RwLock::new(PhysicsData { 
    static ref DATA: Arc<RwLock<PhysicsData>> = Arc::new(RwLock::new(PhysicsData {
        gravity: vector![0.0, 0.0],
        integration_parameters: IntegrationParameters {
            dt: 1.0 / 20.0,
            ..Default::default() },
            ..Default::default()
        },
        island_manager: IslandManager::new(),
        broad_phase: BroadPhase::new(),
        narrow_phase: NarrowPhase::new(),


@@ 156,7 197,8 @@ lazy_static! {
        collider_set: ColliderSet::new(),
        impulse_joint_set: ImpulseJointSet::new(),
        multibody_joint_set: MultibodyJointSet::new(),
        ccd_solver: CCDSolver::new(), }));
        ccd_solver: CCDSolver::new(),
    }));
    static ref ENTITIES: Arc<RwLock<EntityHandler>> = Arc::new(RwLock::new(EntityHandler::new()));
}



@@ 164,10 206,16 @@ lazy_static! {
async fn main() {
    simple_logger::init_with_level(Level::Debug).expect("Unable to start logging service");

    info!("StarKingdoms server (v: {}, build {}) - initializing", env!("STK_VERSION"), env!("STK_BUILD"));
    info!(
        "StarKingdoms server (v: {}, build {}) - initializing",
        env!("STK_VERSION"),
        env!("STK_BUILD")
    );

    if std::env::var("STK_API_KEY").is_err() {
        error!("Unable to read the API key from STK_API_KEY. Ensure it is set, and has a valid value.");
        error!(
            "Unable to read the API key from STK_API_KEY. Ensure it is set, and has a valid value."
        );
        std::process::exit(1);
    }
    if std::env::var("STK_API_URL").is_err() {


@@ 197,8 245,13 @@ async fn main() {
    };

    while let Ok((stream, peer_addr)) = listener.accept().await {
        async_std::task::spawn(handle_request(stream, peer_addr, CMGR.clone(),
                                ENTITIES.clone(), DATA.clone()));
        async_std::task::spawn(handle_request(
            stream,
            peer_addr,
            CMGR.clone(),
            ENTITIES.clone(),
            DATA.clone(),
        ));
    }
}



@@ 206,7 259,7 @@ async fn main() {
pub struct ServerPingResponse {
    pub version: ServerPingResponseVersion,
    pub players: u32,
    pub description: String
    pub description: String,
}

#[derive(Serialize, Deserialize)]


@@ 215,5 268,5 @@ pub struct ServerPingResponseVersion {
    pub number: String,
    pub protocol: u32,
    pub channel: String,
    pub build: String
    pub build: String,
}

M server/src/manager.rs => server/src/manager.rs +117 -70
@@ 1,16 1,21 @@
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use async_std::channel::Sender;
use async_std::sync::RwLock;
use nalgebra::point;
use rapier2d_f64::na::Vector2;
use rapier2d_f64::prelude::{IntegrationParameters, PhysicsPipeline, IslandManager, BroadPhase, NarrowPhase, ImpulseJointSet, MultibodyJointSet, CCDSolver, RigidBodySet, ColliderSet, RigidBodyHandle, ImpulseJointHandle, RigidBodyBuilder, ColliderBuilder, FixedJointBuilder, Real, MassProperties, Isometry};
use async_std::sync::RwLock;
use async_std::channel::Sender;
use rapier2d_f64::prelude::{
    BroadPhase, CCDSolver, ColliderBuilder, ColliderSet, FixedJointBuilder, ImpulseJointHandle,
    ImpulseJointSet, IntegrationParameters, IslandManager, Isometry, MassProperties,
    MultibodyJointSet, NarrowPhase, PhysicsPipeline, Real, RigidBodyBuilder, RigidBodyHandle,
    RigidBodySet,
};
use starkingdoms_protocol::api::APISavedPlayerData;
use starkingdoms_protocol::module::ModuleType;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;

use crate::entity::{get_entity_id, Entity, EntityHandler, EntityId};
use crate::SCALE;
use crate::entity::{EntityId, EntityHandler, Entity, get_entity_id};

#[derive(Clone)]
pub struct ClientManager {


@@ 33,13 38,13 @@ impl Player {
        APISavedPlayerData {}
    }

    pub fn load_api_data(&mut self, _data: &APISavedPlayerData) {

    }
    pub fn load_api_data(&mut self, _data: &APISavedPlayerData) {}
    pub fn search_modules(&self, entities: &EntityHandler) -> Vec<AttachedModule> {
        let mut modules = Vec::new();
        for attachment in self.children.iter().flatten() {
            if let Entity::AttachedModule(child_module) = entities.entities.get(&attachment.child).unwrap() {
            if let Entity::AttachedModule(child_module) =
                entities.entities.get(&attachment.child).unwrap()
            {
                modules.append(&mut child_module.search_modules(entities));
            }
        }


@@ 70,20 75,29 @@ pub struct AttachedModule {
    pub children: [Option<Attachment>; 4],
}
impl AttachedModule {
    pub fn attach(data: &mut PhysicsData, entities: &mut EntityHandler, parent: EntityId,
                  player_id: EntityId, module: Module, attachment_slot: usize) {
    pub fn attach(
        data: &mut PhysicsData,
        entities: &mut EntityHandler,
        parent: EntityId,
        player_id: EntityId,
        module: Module,
        attachment_slot: usize,
    ) {
        let mut entity_map = entities.entities.clone();

        let loose_id = entities.get_from_module(&module).expect("loose module does not exist");
        let loose_body = data.rigid_body_set.get(module.handle).expect("loose module does not exist");
        let parent_entity = entity_map.get_mut(&parent).expect("parent id does not exist");
        let loose_id = entities
            .get_from_module(&module)
            .expect("loose module does not exist");
        let loose_body = data
            .rigid_body_set
            .get(module.handle)
            .expect("loose module does not exist");
        let parent_entity = entity_map
            .get_mut(&parent)
            .expect("parent id does not exist");
        let parent_handle = match parent_entity {
            Entity::Player(player)  => {
                player.handle
            },
            Entity::AttachedModule(module)  => {
                module.handle
            },
            Entity::Player(player) => player.handle,
            Entity::AttachedModule(module) => module.handle,
            _ => {
                panic!("unexpected parent");
            }


@@ 99,12 113,18 @@ impl AttachedModule {
            .rotation(loose_body.rotation().angle())
            .build();
        let attached_handle = data.rigid_body_set.insert(module_body);
        data.collider_set.insert_with_parent(module_collider, attached_handle, &mut data.rigid_body_set);
        data.collider_set.insert_with_parent(
            module_collider,
            attached_handle,
            &mut data.rigid_body_set,
        );
        let attach_joint = FixedJointBuilder::new()
            .local_anchor1(point![0.0, 0.0])
            .local_anchor2(point![0.0, 0.0])
            .build();
        let attach_joint_handle = data.impulse_joint_set.insert(parent_handle, attached_handle, attach_joint, true);
        let attach_joint_handle =
            data.impulse_joint_set
                .insert(parent_handle, attached_handle, attach_joint, true);
        let attached_module = AttachedModule {
            handle: attached_handle,
            module_type: module.module_type,


@@ 118,40 138,48 @@ impl AttachedModule {
                    child: attached_id,
                    connection: attach_joint_handle,
                });
            },
            Entity::AttachedModule(ref mut module)  => {
            }
            Entity::AttachedModule(ref mut module) => {
                module.children[attachment_slot] = Some(Attachment {
                    child: attached_id,
                    connection: attach_joint_handle,
                });
            },
            }
            _ => {
                panic!("unexpected parent");
            }
        };
        entity_map.insert(attached_id, Entity::AttachedModule(attached_module));
        // delete loose module
        data.rigid_body_set.remove(module.handle,
                                   &mut data.island_manager,
                                   &mut data.collider_set,
                                   &mut data.impulse_joint_set,
                                   &mut data.multibody_joint_set, true);
        data.rigid_body_set.remove(
            module.handle,
            &mut data.island_manager,
            &mut data.collider_set,
            &mut data.impulse_joint_set,
            &mut data.multibody_joint_set,
            true,
        );
        entities.entities.remove(&loose_id);
    }
    pub fn attach_new(data: &mut PhysicsData, entities: &mut EntityHandler, parent: EntityId,
                  player_id: EntityId, module: ModuleTemplate, attachment_slot: usize, rotation: f64) {
    pub fn attach_new(
        data: &mut PhysicsData,
        entities: &mut EntityHandler,
        parent: EntityId,
        player_id: EntityId,
        module: ModuleTemplate,
        attachment_slot: usize,
        rotation: f64,
    ) {
        let mut entity_map = entities.entities.clone();

        //let loose_id = entities.get_from_module(&module).expect("loose module does not exist");
        //let loose_body = data.rigid_body_set.get(module.handle).expect("loose module does not exist");
        let parent_entity = entity_map.get_mut(&parent).expect("parent id does not exist");
        let parent_entity = entity_map
            .get_mut(&parent)
            .expect("parent id does not exist");
        let parent_handle = match parent_entity {
            Entity::Player(player)  => {
                player.handle
            },
            Entity::AttachedModule(module)  => {
                module.handle
            },
            Entity::Player(player) => player.handle,
            Entity::AttachedModule(module) => module.handle,
            _ => {
                panic!("unexpected parent");
            }


@@ 165,17 193,23 @@ impl AttachedModule {
            .rotation(module.heading)
            .build();
        let attached_handle = data.rigid_body_set.insert(module_body);
        data.collider_set.insert_with_parent(module_collider, attached_handle, &mut data.rigid_body_set);
        data.collider_set.insert_with_parent(
            module_collider,
            attached_handle,
            &mut data.rigid_body_set,
        );
        let anchor = point![
            -0. / SCALE * rotation.cos() +100. / SCALE * rotation.sin(), 
            -0. / SCALE * rotation.sin() -100. / SCALE * rotation.cos()
            -0. / SCALE * rotation.cos() + 100. / SCALE * rotation.sin(),
            -0. / SCALE * rotation.sin() - 100. / SCALE * rotation.cos()
        ];
        let attach_joint = FixedJointBuilder::new()
            .local_anchor1(anchor)
            .local_anchor2(point![0.0, 0.0 / SCALE])
            .local_frame2(Isometry::rotation(rotation))
            .build();
        let attach_joint_handle = data.impulse_joint_set.insert(parent_handle, attached_handle, attach_joint, true);
        let attach_joint_handle =
            data.impulse_joint_set
                .insert(parent_handle, attached_handle, attach_joint, true);
        let attached_module = AttachedModule {
            handle: attached_handle,
            module_type: module.module_type,


@@ 189,13 223,13 @@ impl AttachedModule {
                    child: attached_id,
                    connection: attach_joint_handle,
                });
            },
            Entity::AttachedModule(ref mut module)  => {
            }
            Entity::AttachedModule(ref mut module) => {
                module.children[attachment_slot] = Some(Attachment {
                    child: attached_id,
                    connection: attach_joint_handle,
                });
            },
            }
            _ => {
                panic!("unexpected parent");
            }


@@ 215,7 249,9 @@ impl AttachedModule {
    pub fn search_modules(&self, entities: &EntityHandler) -> Vec<AttachedModule> {
        let mut modules = Vec::new();
        for attachment in self.children.iter().flatten() {
            if let Entity::AttachedModule(child_module) = entities.entities.get(&attachment.child).unwrap() {
            if let Entity::AttachedModule(child_module) =
                entities.entities.get(&attachment.child).unwrap()
            {
                modules.append(&mut child_module.search_modules(entities));
            }
        }


@@ 234,19 270,20 @@ pub struct PlayerInput {
    pub up: bool,
    pub left: bool,
    pub right: bool,
    pub down: bool
    pub down: bool,
}

#[derive(Clone)]
pub struct ClientHandler {
    pub tx: Sender<ClientHandlerMessage>
    pub tx: Sender<ClientHandlerMessage>,
}

#[derive(Clone, Default)]
pub struct PhysicsData {
    pub gravity: Vector2<f64>,
    pub integration_parameters: IntegrationParameters,
    pub island_manager: IslandManager, pub broad_phase: BroadPhase,
    pub island_manager: IslandManager,
    pub broad_phase: BroadPhase,
    pub narrow_phase: NarrowPhase,
    pub rigid_body_set: RigidBodySet,
    pub collider_set: ColliderSet,


@@ 256,28 293,38 @@ pub struct PhysicsData {
}
impl PhysicsData {
    pub fn tick(&mut self, pipeline: &mut PhysicsPipeline) {
        pipeline.step(&self.gravity,
                           &self.integration_parameters,
                           &mut self.island_manager,
                           &mut self.broad_phase,
                           &mut self.narrow_phase,
                           &mut self.rigid_body_set,
                           &mut self.collider_set,
                           &mut self.impulse_joint_set,
                           &mut self.multibody_joint_set,
                           &mut self.ccd_solver,
                           None,
                           &(),
                           &()
                        );
        pipeline.step(
            &self.gravity,
            &self.integration_parameters,
            &mut self.island_manager,
            &mut self.broad_phase,
            &mut self.narrow_phase,
            &mut self.rigid_body_set,
            &mut self.collider_set,
            &mut self.impulse_joint_set,
            &mut self.multibody_joint_set,
            &mut self.ccd_solver,
            None,
            &(),
            &(),
        );
    }
}

#[derive(Debug, Clone)]
pub enum ClientHandlerMessage {
    Tick,
    ChatMessage { from: String, message: String },
    PlayersUpdate { players: Vec<starkingdoms_protocol::player::Player> },
    PlanetData { planets: Vec<starkingdoms_protocol::planet::Planet> },
    ModulesUpdate { modules: Vec<starkingdoms_protocol::module::Module> },
    ChatMessage {
        from: String,
        message: String,
    },
    PlayersUpdate {
        players: Vec<starkingdoms_protocol::player::Player>,
    },
    PlanetData {
        planets: Vec<starkingdoms_protocol::planet::Planet>,
    },
    ModulesUpdate {
        modules: Vec<starkingdoms_protocol::module::Module>,
    },
}

M server/src/orbit/constants.rs => server/src/orbit/constants.rs +2 -2
@@ 22,6 22,6 @@ pub const MOON_RADIUS: f64 = MOON_RADIUS_RL * GAME_SCALE_DISTANCE * MOON_RADIUS_
pub const MOON_MASS_RL: f64 = 73476730900000000000000.0;
pub const MOON_MASS: f64 = MOON_MASS_RL * GAME_SCALE_MASS * MOON_MASS_BIAS;
pub const MOON_PERIAPSIS: f64 = 363228900.0 * GAME_SCALE_DISTANCE * MOON_PERIAPSIS_BIAS;
pub const MOON_APOAPSIS: f64 = 405400000.0  * GAME_SCALE_DISTANCE * MOON_APOAPSIS_BIAS;
pub const MOON_APOAPSIS: f64 = 405400000.0 * GAME_SCALE_DISTANCE * MOON_APOAPSIS_BIAS;
pub const MOON_ORBIT_TIME_RL: f64 = 2332800.0;
pub const MOON_ORBIT_TIME: f64 = MOON_ORBIT_TIME_RL * GAME_SCALE_TIME * MOON_ORBIT_TIME_BIAS;
\ No newline at end of file
pub const MOON_ORBIT_TIME: f64 = MOON_ORBIT_TIME_RL * GAME_SCALE_TIME * MOON_ORBIT_TIME_BIAS;

M server/src/orbit/kepler.rs => server/src/orbit/kepler.rs +1 -1
@@ 4,4 4,4 @@
/// e is the eccentricity of the orbit (0 = perfect circle, and up to 1 is increasingly elliptical)
pub fn kepler_equation(eccentric_anomaly: f64, mean_anomaly: f64, eccentricity: f64) -> f64 {
    mean_anomaly - eccentric_anomaly + eccentricity * eccentric_anomaly.sin()
}
\ No newline at end of file
}

M server/src/orbit/mod.rs => server/src/orbit/mod.rs +3 -3
@@ 1,6 1,6 @@
pub mod constants;
pub mod kepler;
pub mod newtonian;
#[allow(clippy::module_inception)]
pub mod orbit;
pub mod newtonian;
pub mod kepler;
pub mod vis_viva;
\ No newline at end of file
pub mod vis_viva;

M server/src/orbit/newtonian.rs => server/src/orbit/newtonian.rs +8 -3
@@ 3,7 3,11 @@ use crate::orbit::kepler::kepler_equation;
pub const NEWTONIAN_STEP_SIZE: f64 = 0.0001;
pub const NEWTONIAN_ACCEPTABLE_ERROR: f64 = 0.00000001;

pub fn solve_kepler_with_newtonian(mean_anomaly: f64, eccentricity: f64, max_iterations: u64) -> f64 {
pub fn solve_kepler_with_newtonian(
    mean_anomaly: f64,
    eccentricity: f64,
    max_iterations: u64,
) -> f64 {
    let mut guess = mean_anomaly;

    for _ in 0..max_iterations {


@@ 15,10 19,11 @@ pub fn solve_kepler_with_newtonian(mean_anomaly: f64, eccentricity: f64, max_ite
        }

        // otherwise, update guess
        let slope = (kepler_equation(guess + NEWTONIAN_STEP_SIZE, mean_anomaly, eccentricity) - y) / NEWTONIAN_STEP_SIZE;
        let slope = (kepler_equation(guess + NEWTONIAN_STEP_SIZE, mean_anomaly, eccentricity) - y)
            / NEWTONIAN_STEP_SIZE;
        let step = y / slope;
        guess -= step;
    }

    guess
}
\ No newline at end of file
}

M server/src/orbit/orbit.rs => server/src/orbit/orbit.rs +23 -6
@@ 1,15 1,28 @@
// Mostly stolen from SebLague's plane game
// thanks

use nalgebra::{vector, Vector2};
use crate::orbit::newtonian::solve_kepler_with_newtonian;
use nalgebra::{vector, Vector2};

#[allow(clippy::too_many_arguments)]
pub fn calculate_vector_of_orbit(periapsis: f64, apoapsis: f64, t: f64, current_x: f64, current_y: f64, orbiting_x: f64, orbiting_y: f64, mass: f64, step: f64) -> Vector2<f64> {
pub fn calculate_vector_of_orbit(
    periapsis: f64,
    apoapsis: f64,
    t: f64,
    current_x: f64,
    current_y: f64,
    orbiting_x: f64,
    orbiting_y: f64,
    mass: f64,
    step: f64,
) -> Vector2<f64> {
    let semi_major_length = (apoapsis + periapsis) / 2.0;
    let _linear_eccentricity = semi_major_length - periapsis; // distance between center and focus

    let target = calculate_world_position_of_orbit(calculate_point_on_orbit(periapsis, apoapsis, t), vector![orbiting_x, orbiting_y]);
    let target = calculate_world_position_of_orbit(
        calculate_point_on_orbit(periapsis, apoapsis, t),
        vector![orbiting_x, orbiting_y],
    );
    let target_x = target[0];
    let target_y = target[1];



@@ 32,7 45,8 @@ pub fn calculate_point_on_orbit(periapsis: f64, apoapsis: f64, t: f64) -> Vector
    let semi_major_length = (apoapsis + periapsis) / 2.0;
    let linear_eccentricity = semi_major_length - periapsis; // distance between center and focus
    let eccentricity = linear_eccentricity / semi_major_length; // 0: circle. 1: parabola. in between: ellipse
    let semi_minor_length = (semi_major_length * semi_major_length - linear_eccentricity * linear_eccentricity).sqrt();
    let semi_minor_length =
        (semi_major_length * semi_major_length - linear_eccentricity * linear_eccentricity).sqrt();

    let mean_anomaly = t * std::f64::consts::PI * 2.0;
    let eccentric_anomaly = solve_kepler_with_newtonian(mean_anomaly, eccentricity, 100);


@@ 44,8 58,11 @@ pub fn calculate_point_on_orbit(periapsis: f64, apoapsis: f64, t: f64) -> Vector
    vector![point_x, point_y]
}

pub fn calculate_world_position_of_orbit(point: Vector2<f64>, orbiting_on: Vector2<f64>) -> Vector2<f64> {
pub fn calculate_world_position_of_orbit(
    point: Vector2<f64>,
    orbiting_on: Vector2<f64>,
) -> Vector2<f64> {
    // i have no idea if this is actually right or not
    // we'll find out
    vector![point[0] + orbiting_on[0], point[1] + orbiting_on[1]]
}
\ No newline at end of file
}

M server/src/orbit/vis_viva.rs => server/src/orbit/vis_viva.rs +7 -2
@@ 1,3 1,8 @@
pub fn vis_viva(distance_between_centers: f64, semi_major: f64, g: f64, mass_of_bigger: f64) -> f64 {
pub fn vis_viva(
    distance_between_centers: f64,
    semi_major: f64,
    g: f64,
    mass_of_bigger: f64,
) -> f64 {
    (g * mass_of_bigger * (2.0 / distance_between_centers - 1.0 / semi_major)).sqrt()
}
\ No newline at end of file
}

M server/src/planet.rs => server/src/planet.rs +51 -31
@@ 1,12 1,16 @@
use std::collections::HashMap;
use nalgebra::{Vector2, vector};
use rapier2d_f64::prelude::{RigidBodyHandle, RigidBodySet, ColliderBuilder, RigidBodyBuilder, ColliderSet};
use nalgebra::{vector, Vector2};
use rapier2d_f64::prelude::{
    ColliderBuilder, ColliderSet, RigidBodyBuilder, RigidBodyHandle, RigidBodySet,
};
use starkingdoms_protocol::planet::PlanetType;
use std::collections::HashMap;

use crate::entity::{Entities, get_entity_id, Entity, EntityId};
use crate::{SCALE, manager::ClientHandlerMessage};
use crate::orbit::constants::{EARTH_MASS, EARTH_RADIUS, MOON_APOAPSIS, MOON_MASS, MOON_PERIAPSIS, MOON_RADIUS};
use crate::entity::{get_entity_id, Entities, Entity, EntityId};
use crate::orbit::constants::{
    EARTH_MASS, EARTH_RADIUS, MOON_APOAPSIS, MOON_MASS, MOON_PERIAPSIS, MOON_RADIUS,
};
use crate::orbit::orbit::{calculate_point_on_orbit, calculate_world_position_of_orbit};
use crate::{manager::ClientHandlerMessage, SCALE};

//const GRAVITY: f64 = 0.001;
pub const GRAVITY: f64 = 12.6674;


@@ 17,14 21,17 @@ pub struct Planet {
    pub body_handle: RigidBodyHandle,
    pub position: (f64, f64),
    pub radius: f64,
    pub mass: f64
    pub mass: f64,
}

impl Planet {
    pub fn gravity(&self, position: (f64, f64), mass: f64) -> (f64, f64) {
        let distance = ((position.0 - self.position.0).powi(2) + (position.1 - self.position.1).powi(2)).sqrt();
        let distance = ((position.0 - self.position.0).powi(2)
            + (position.1 - self.position.1).powi(2))
        .sqrt();
        let force = GRAVITY * ((self.mass * mass) / (distance * distance));
        let mut direction = Vector2::new(self.position.0 - position.0, self.position.1 - position.1);
        let mut direction =
            Vector2::new(self.position.0 - position.0, self.position.1 - position.1);
        direction.set_magnitude(force);
        (direction.x, direction.y)
    }


@@ 44,12 51,16 @@ impl Planets {
        self.planets.get_mut(planet_id)
    }

    pub async fn make_planet(_planet_id: &str,
                             planet_type: PlanetType, mass: f64, radius: f64,
                             position: (f64, f64), rigid_body_set: &mut RigidBodySet, collider_set: &mut ColliderSet
                            ) -> (EntityId, Entity) {
        let collider = ColliderBuilder::ball(radius / SCALE)
            .build();
    pub async fn make_planet(
        _planet_id: &str,
        planet_type: PlanetType,
        mass: f64,
        radius: f64,
        position: (f64, f64),
        rigid_body_set: &mut RigidBodySet,
        collider_set: &mut ColliderSet,
    ) -> (EntityId, Entity) {
        let collider = ColliderBuilder::ball(radius / SCALE).build();
        let body = RigidBodyBuilder::kinematic_position_based()
            .translation(vector![position.0 / SCALE, position.1 / SCALE])
            .dominance_group(127)


@@ 59,17 70,23 @@ impl Planets {
        collider_set.insert_with_parent(collider, body_handle, rigid_body_set);

        let entity_id = get_entity_id();
        (entity_id, Entity::Planet(Planet {
            planet_type,
            body_handle,
            position,
            radius,
            mass,
        }))
        (
            entity_id,
            Entity::Planet(Planet {
                planet_type,
                body_handle,
                position,
                radius,
                mass,
            }),
        )
    }

    pub async fn create_planets(rigid_body_set: &mut RigidBodySet, collider_set: &mut ColliderSet,
                                entities: &mut Entities) -> Vec<EntityId> {
    pub async fn create_planets(
        rigid_body_set: &mut RigidBodySet,
        collider_set: &mut ColliderSet,
        entities: &mut Entities,
    ) -> Vec<EntityId> {
        let mut planet_ids: Vec<EntityId> = Vec::new();
        let (earth_id, entity) = Planets::make_planet(
            "earth",


@@ 79,13 96,17 @@ impl Planets {
            (100.0, 100.0),
            rigid_body_set,
            collider_set,
        ).await;
        )
        .await;
        entities.insert(earth_id, entity);
        planet_ids.push(earth_id);

        let moon_start_point;
        if let Entity::Planet(earth) = entities.get(&earth_id).unwrap() {
            moon_start_point = calculate_world_position_of_orbit(calculate_point_on_orbit(MOON_PERIAPSIS, MOON_APOAPSIS, 0.0), vector![earth.position.0, earth.position.1]);
            moon_start_point = calculate_world_position_of_orbit(
                calculate_point_on_orbit(MOON_PERIAPSIS, MOON_APOAPSIS, 0.0),
                vector![earth.position.0, earth.position.1],
            );
        } else {
            moon_start_point = vector![0., 0.];
        }


@@ 97,8 118,9 @@ impl Planets {
            MOON_RADIUS,
            (moon_start_point[0], moon_start_point[1]),
            rigid_body_set,
            collider_set
        ).await;
            collider_set,
        )
        .await;
        entities.insert(moon_id, moon);
        planet_ids.push(moon_id);
        planet_ids


@@ 118,9 140,7 @@ impl Planets {
            });
        }

        ClientHandlerMessage::PlanetData {
            planets
        }
        ClientHandlerMessage::PlanetData { planets }
    }

    pub fn gravity(&self, position: (f64, f64), mass: f64) -> (f64, f64) {

M server/src/timer.rs => server/src/timer.rs +125 -58
@@ 1,22 1,35 @@
use std::{time::Duration, sync::Arc, f64::consts::PI};
use log::{warn, info};
use nalgebra::{vector, point};
use rand::Rng;
use rapier2d_f64::prelude::{PhysicsPipeline, ColliderBuilder, RigidBodyBuilder, MassProperties, RigidBodyHandle};
use async_std::sync::RwLock;
use async_std::task::sleep;
use starkingdoms_protocol::{player::Player, planet::PlanetType, module::ModuleType};
use crate::{manager::{ClientHandlerMessage, ClientManager, PhysicsData, Module}, SCALE, planet::{Planets, Planet}, entity::{get_entity_id, Entity}};
use crate::entity::EntityHandler;
use crate::orbit::constants::{GAME_ORBITS_ENABLED, MOON_APOAPSIS, MOON_ORBIT_TIME, MOON_PERIAPSIS};
use crate::orbit::constants::{
    GAME_ORBITS_ENABLED, MOON_APOAPSIS, MOON_ORBIT_TIME, MOON_PERIAPSIS,
};
use crate::orbit::orbit::{calculate_point_on_orbit, calculate_world_position_of_orbit};
use crate::{
    entity::{get_entity_id, Entity},
    manager::{ClientHandlerMessage, ClientManager, Module, PhysicsData},
    planet::{Planet, Planets},
    SCALE,
};
use async_std::sync::RwLock;
use async_std::task::sleep;
use log::{info, warn};
use nalgebra::{point, vector};
use rand::Rng;
use rapier2d_f64::prelude::{
    ColliderBuilder, MassProperties, PhysicsPipeline, RigidBodyBuilder, RigidBodyHandle,
};
use starkingdoms_protocol::{module::ModuleType, planet::PlanetType, player::Player};
use std::{f64::consts::PI, sync::Arc, time::Duration};

pub const ROTATIONAL_FORCE: f64 = 100.0;
pub const LATERAL_FORCE: f64 = 100.0;
pub const MODULE_SPAWN: f64 = 3.0;
pub const MODULE_MAX: u32 = 10;

pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<PhysicsData>>, entities: Arc<RwLock<EntityHandler>>) {
pub async fn timer_main(
    mgr: ClientManager,
    physics_data_orig: Arc<RwLock<PhysicsData>>,
    entities: Arc<RwLock<EntityHandler>>,
) {
    let mut pipeline = PhysicsPipeline::new();

    let mut time = 0.0;


@@ 29,7 42,12 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
        let mut rigid_body_set = data_handle.rigid_body_set.clone();
        let mut collider_set = data_handle.collider_set.clone();

        _planet_ids = Planets::create_planets(&mut rigid_body_set, &mut collider_set, &mut entities.write().await.entities).await;
        _planet_ids = Planets::create_planets(
            &mut rigid_body_set,
            &mut collider_set,
            &mut entities.write().await.entities,
        )
        .await;

        data_handle.rigid_body_set = rigid_body_set;
        data_handle.collider_set = collider_set;


@@ 55,10 73,19 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic

            // update moon
            let moon: &mut Planet = &mut planets.get_planet(PlanetType::Moon).unwrap();
            let new_moon_position = calculate_world_position_of_orbit(calculate_point_on_orbit(MOON_PERIAPSIS, MOON_APOAPSIS, time / MOON_ORBIT_TIME), new_earth_position);
            let moon_body = physics_data.rigid_body_set.get_mut(moon.body_handle).unwrap();
            let new_moon_position = calculate_world_position_of_orbit(
                calculate_point_on_orbit(MOON_PERIAPSIS, MOON_APOAPSIS, time / MOON_ORBIT_TIME),
                new_earth_position,
            );
            let moon_body = physics_data
                .rigid_body_set
                .get_mut(moon.body_handle)
                .unwrap();
            moon_body.set_next_kinematic_position(new_moon_position.into());
            moon.position = (moon_body.translation()[0] / SCALE, moon_body.translation()[1] / SCALE);
            moon.position = (
                moon_body.translation()[0] / SCALE,
                moon_body.translation()[1] / SCALE,
            );
        }

        physics_data.tick(&mut pipeline);


@@ 66,7 93,8 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
        let mut protocol_players = vec![];

        {
            if module_timer > MODULE_SPAWN && entities.read().await.get_module_count() < MODULE_MAX {
            if module_timer > MODULE_SPAWN && entities.read().await.get_module_count() < MODULE_MAX
            {
                module_timer = 0.;

                let mut rigid_body_set = physics_data.rigid_body_set.clone();


@@ 81,10 109,17 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
                    rng.gen::<f64>() * PI * 2.
                };
                let module_body = RigidBodyBuilder::dynamic()
                    .translation(vector![angle.cos() * 2050. / SCALE, angle.sin() * 2050.0/SCALE])
                    .translation(vector![
                        angle.cos() * 2050. / SCALE,
                        angle.sin() * 2050.0 / SCALE
                    ])
                    .build();
                let module_handler = rigid_body_set.insert(module_body);
                collider_set.insert_with_parent(module_collider, module_handler, &mut rigid_body_set);
                collider_set.insert_with_parent(
                    module_collider,
                    module_handler,
                    &mut rigid_body_set,
                );

                physics_data.rigid_body_set = rigid_body_set;
                physics_data.collider_set = collider_set;


@@ 94,7 129,11 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
                    module_type: ModuleType::Cargo,
                    lifetime: 0.0,
                };
                entities.write().await.entities.insert(get_entity_id(), Entity::Module(module));
                entities
                    .write()
                    .await
                    .entities
                    .insert(get_entity_id(), Entity::Module(module));
            }
            let mut entities = entities.write().await;
            for module in entities.get_modules().iter_mut() {


@@ 102,11 141,14 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
                let module_body = physics_data.rigid_body_set.get_mut(module_handle).unwrap();
                module_body.reset_forces(true);
                module_body.reset_torques(true);
                let grav_force = entities.gravity((module_body.translation().x, module_body.translation().y), module_body.mass());
                let grav_force = entities.gravity(
                    (module_body.translation().x, module_body.translation().y),
                    module_body.mass(),
                );
                module_body.apply_impulse(vector![grav_force.0, grav_force.1], true);
                let id = entities.get_from_module(module).unwrap();
                if let Entity::Module(p_module) = entities.entities.get_mut(&id).unwrap() {
                    p_module.lifetime += 5./1000.;
                    p_module.lifetime += 5. / 1000.;
                }
                if module.lifetime > 80. {
                    let mut rigid_body_set = physics_data.rigid_body_set.clone();


@@ 114,8 156,14 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
                    let mut collider_set = physics_data.collider_set.clone();
                    let mut impulse_joint_set = physics_data.impulse_joint_set.clone();
                    let mut multibody_joint_set = physics_data.multibody_joint_set.clone();
                    rigid_body_set.remove(module.handle, &mut island_manager, &mut collider_set,
                                            &mut impulse_joint_set, &mut multibody_joint_set, true);
                    rigid_body_set.remove(
                        module.handle,
                        &mut island_manager,
                        &mut collider_set,
                        &mut impulse_joint_set,
                        &mut multibody_joint_set,
                        true,
                    );
                    physics_data.rigid_body_set = rigid_body_set;
                    physics_data.collider_set = collider_set;
                    physics_data.island_manager = island_manager;


@@ 133,7 181,10 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
                player_body.reset_forces(true);
                player_body.reset_torques(true);
                let planets = entities.read().await;
                let grav_force = planets.gravity((player_body.translation().x, player_body.translation().y), player_body.mass());
                let grav_force = planets.gravity(
                    (player_body.translation().x, player_body.translation().y),
                    player_body.mass(),
                );
                player_body.apply_impulse(vector![grav_force.0, grav_force.1], true);

                let mut left_top_thruster: f64 = 0.0;


@@ 182,35 233,26 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
                ];
                let scale = SCALE;
                let top_left_point = point![
                    -25. / scale * rotation.cos() +25. / scale * rotation.sin(), 
                    -25. / scale * rotation.sin() -25. / scale * rotation.cos()
                    -25. / scale * rotation.cos() + 25. / scale * rotation.sin(),
                    -25. / scale * rotation.sin() - 25. / scale * rotation.cos()
                ] + player_body.translation();
                let top_right_point = point![
                     25. / scale * rotation.cos() +25. / scale * rotation.sin(), 
                     25. / scale * rotation.sin() -25. / scale * rotation.cos()
                    25. / scale * rotation.cos() + 25. / scale * rotation.sin(),
                    25. / scale * rotation.sin() - 25. / scale * rotation.cos()
                ] + player_body.translation();
                let bottom_left_point = point![
                    -25. / scale * rotation.cos() -25. / scale * rotation.sin(), 
                    -25. / scale * rotation.sin() +25. / scale * rotation.cos()
                    -25. / scale * rotation.cos() - 25. / scale * rotation.sin(),
                    -25. / scale * rotation.sin() + 25. / scale * rotation.cos()
                ] + player_body.translation();
                let bottom_right_point = point![
                     25. / scale * rotation.cos() -25. / scale * rotation.sin(), 
                     25. / scale * rotation.sin() +25. / scale * rotation.cos()
                    25. / scale * rotation.cos() - 25. / scale * rotation.sin(),
                    25. / scale * rotation.sin() + 25. / scale * rotation.cos()
                ] + player_body.translation();

                player_body.add_force_at_point(
                    left_top_thruster,
                    top_left_point, true);
                player_body.add_force_at_point(
                    right_top_thruster,
                    top_right_point, true);
                player_body.add_force_at_point(
                    left_bottom_thruster,
                    bottom_left_point, true);
                player_body.add_force_at_point(
                    right_bottom_thruster,
                    bottom_right_point, true);

                player_body.add_force_at_point(left_top_thruster, top_left_point, true);
                player_body.add_force_at_point(right_top_thruster, top_right_point, true);
                player_body.add_force_at_point(left_bottom_thruster, bottom_left_point, true);
                player_body.add_force_at_point(right_bottom_thruster, bottom_right_point, true);

                let translation = player_body.translation();



@@ 239,7 281,13 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
        for (addr, client_thread) in mgr_r.iter() {
            match client_thread.tx.send(ClientHandlerMessage::Tick).await {
                Ok(_) => {
                    match client_thread.tx.send(ClientHandlerMessage::PlayersUpdate {players: protocol_players.clone()}).await {
                    match client_thread
                        .tx
                        .send(ClientHandlerMessage::PlayersUpdate {
                            players: protocol_players.clone(),
                        })
                        .await
                    {
                        Ok(_) => (),
                        Err(e) => {
                            warn!("unable to send position packet: {}", e);


@@ 247,20 295,32 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
                    };
                    let mut modules = entities.read().await.get_modules();
                    let attached_modules = entities.read().await.get_all_attached();
                    let attached_handles: Vec<RigidBodyHandle> = attached_modules.iter().map(|m| {
                        m.handle
                    }).collect();
                    modules.append(&mut attached_modules.iter().map(|m| {
                        let module = m.to_module();
                        info!("{:?}", module);
                        module
                    }).collect());
                    let attached_handles: Vec<RigidBodyHandle> =
                        attached_modules.iter().map(|m| m.handle).collect();
                    modules.append(
                        &mut attached_modules
                            .iter()
                            .map(|m| {
                                let module = m.to_module();
                                info!("{:?}", module);
                                module
                            })
                            .collect(),
                    );
                    modules.iter().for_each(|module| {
                        if attached_handles.contains(&module.handle) {
                            info!("{:?}", physics_data.rigid_body_set.get(module.handle).unwrap().translation());
                            info!(
                                "{:?}",
                                physics_data
                                    .rigid_body_set
                                    .get(module.handle)
                                    .unwrap()
                                    .translation()
                            );
                        }
                    });
                    let protocol_modules: Vec<starkingdoms_protocol::module::Module> = modules.iter()
                    let protocol_modules: Vec<starkingdoms_protocol::module::Module> = modules
                        .iter()
                        .map(|module| {
                            let body = physics_data.rigid_body_set.get(module.handle).unwrap();
                            return starkingdoms_protocol::module::Module {


@@ 270,8 330,15 @@ pub async fn timer_main(mgr: ClientManager, physics_data_orig: Arc<RwLock<Physic
                                y: body.translation().y * SCALE,
                                special_fields: Default::default(),
                            };
                        }).collect();
                    match client_thread.tx.send(ClientHandlerMessage::ModulesUpdate { modules: protocol_modules.clone() }).await {
                        })
                        .collect();
                    match client_thread
                        .tx
                        .send(ClientHandlerMessage::ModulesUpdate {
                            modules: protocol_modules.clone(),
                        })
                        .await
                    {
                        Ok(_) => (),
                        Err(e) => {
                            warn!("unable to send module position packet: {}", e);