diff --git a/src/entity/mod.rs b/src/entity/mod.rs index 8b13789..f28d7c2 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -1 +1 @@ - +pub mod player; diff --git a/src/entity/player.rs b/src/entity/player.rs new file mode 100644 index 0000000..a2b15f2 --- /dev/null +++ b/src/entity/player.rs @@ -0,0 +1,8 @@ +use crate::world::location::Location; +use crate::server::NetworkClient; + +pub struct Player { + position: Location, + display_name: String, + connection: NetworkClient, +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 0003064..c996692 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -7,11 +7,13 @@ use packets::*; use serde_json::json; use std::sync::mpsc::{self, Receiver, TryRecvError}; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; +use crate::entity::player::Player; /// The struct containing all the data and running all the updates. pub struct Server { - pub network_clients: Vec, + network_clients: Vec, network_receiver: Receiver, + pub players: Vec, } impl Server { pub fn new(addr: A) -> Server { @@ -26,12 +28,7 @@ impl Server { .accept() .await .expect("Network receiver disconnected"); - tx.send(NetworkClient { - id: id as u128, - connected: true, - stream, - state: NetworkClientState::Handshake, - }) + tx.send(NetworkClient::new(stream, id as u128)) .expect("Network receiver disconnected"); id += 1; } @@ -40,6 +37,7 @@ impl Server { Server { network_receiver: rx, network_clients: vec![], + players: vec![], } } @@ -101,8 +99,22 @@ pub struct NetworkClient { pub connected: bool, pub stream: TcpStream, pub state: NetworkClientState, + pub uuid: Option, + pub username: Option, } impl NetworkClient { + /// Create a new `NetworkClient` + pub fn new(stream: TcpStream, id: u128) -> NetworkClient { + NetworkClient { + id, + connected: true, + stream, + state: NetworkClientState::Handshake, + uuid: None, + username: None, + } + } + /// Update the client. /// /// Updating could mean connecting new clients, reading packets, @@ -156,7 +168,6 @@ impl NetworkClient { "description": { "text": CONFIG.motd }, - // TODO: Dynamically send the icon instead of linking statically. "favicon": format!("data:image/png;base64,{}", if FAVICON.is_ok() { radix64::STD.encode(FAVICON.as_ref().unwrap().as_slice()) } else { "".to_owned() }) }) .to_string() @@ -179,13 +190,25 @@ impl NetworkClient { let loginstart = LoginStart::read(&mut self.stream).await.unwrap(); debug!("{:?}", loginstart); // Offline mode skips encryption and compression. + // TODO: Encryption and compression let mut loginsuccess = LoginSuccess::new(); // We're in offline mode, so this is a temporary uuid. + // TODO: Get uuid and username from Mojang servers. loginsuccess.uuid = "00000000-0000-3000-0000-000000000000".into(); loginsuccess.username = loginstart.player_name; loginsuccess.write(&mut self.stream).await.unwrap(); debug!("{:?}", loginsuccess); + self.uuid = Some(loginsuccess.uuid.clone().into()); + self.username = Some(loginsuccess.username.clone().into()); self.state = NetworkClientState::Play; + let joingame = JoinGame::new(); + /// TODO: Fill out `joingame` with actual information. + joingame.write(&mut self.stream).await.unwrap(); + debug!("{:?}", joingame); + let (packet_length, packet_id) = + read_packet_header(&mut self.stream).await.unwrap(); + let clientsettings = ClientSettings::read(&mut self.stream).await.unwrap(); + debug!("{:?}", clientsettings); } NetworkClientState::Play => {} NetworkClientState::Disconnected => { diff --git a/src/server/packets/clientbound.rs b/src/server/packets/clientbound.rs index 03deb2f..daab897 100644 --- a/src/server/packets/clientbound.rs +++ b/src/server/packets/clientbound.rs @@ -1,4 +1,5 @@ use crate::mctypes::*; +use crate::CONFIG; use std::convert::{Into, TryFrom}; use tokio::net::TcpStream; @@ -163,3 +164,66 @@ impl LoginDisconnect { Ok(()) } } + +#[derive(Debug, Clone)] +pub struct JoinGame { + entity_id: MCInt, // The player's Entity ID (EID) + gamemode: MCUnsignedByte, // 0: Survival, 1: Creative, 2: Adventure, 3: Spectator. Bit 3 (0x8) is the hardcore flag. + dimension: MCByte, // -1: Nether, 0: Overworld, 1: End + difficulty: MCUnsignedByte, // 0: Peaceful, 1: Easy, 2: Normal, 3: Hard + max_players: MCUnsignedByte, // Used by the client to draw the player list + level_type: MCString, // default, flat, largeBiomes, amplified, default_1_1 + reduced_debug_info: MCBoolean, // If true, a Notchian client shows reduced information on the debug screen. +} +impl Into> for JoinGame { + fn into(self) -> Vec { + let mut out = vec![]; + let mut temp: Vec = MCVarInt::from(0x01).into(); // 0x01 Join Game. + temp.extend_from_slice(&Into::>::into(self.entity_id)); + temp.extend_from_slice(&Into::>::into(self.gamemode)); + temp.extend_from_slice(&Into::>::into(self.dimension)); + temp.extend_from_slice(&Into::>::into(self.difficulty)); + temp.extend_from_slice(&Into::>::into(self.max_players)); + temp.extend_from_slice(&Into::>::into(self.level_type)); + temp.extend_from_slice(&Into::>::into(self.reduced_debug_info)); + out.extend_from_slice(&Into::>::into(MCVarInt::from(temp.len() as i32))); + out.extend_from_slice(&temp); + out + } +} +impl TryFrom> for JoinGame { + type Error = &'static str; + fn try_from(_bytes: Vec) -> Result { + Err("unimplemented") + } +} +impl JoinGame { + pub fn new() -> Self { + JoinGame { + entity_id: 0.into(), + gamemode: 1.into(), // Default to creative mode. + dimension: 0.into(), // Default to overworld. + difficulty: 2.into(), + max_players: (CONFIG.max_players as u8).into(), + level_type: "default".into(), // Use the default world type. + reduced_debug_info: false.into(), // The debug info should be useful. + } + } + pub async fn read(t: &mut TcpStream) -> tokio::io::Result { + let mut joingame = JoinGame::new(); + joingame.entity_id = MCInt::read(t).await?; + joingame.gamemode = MCUnsignedByte::read(t).await?; + joingame.dimension = MCByte::read(t).await?; + joingame.difficulty = MCUnsignedByte::read(t).await?; + joingame.max_players = MCUnsignedByte::read(t).await?; + joingame.level_type = MCString::read(t).await?; + joingame.reduced_debug_info = MCBoolean::read(t).await?; + Ok(joingame) + } + pub async fn write(&self, t: &mut TcpStream) -> tokio::io::Result<()> { + for b in Into::>::into(self.clone()) { + write_byte(t, b).await?; + } + Ok(()) + } +} diff --git a/src/server/packets/serverbound.rs b/src/server/packets/serverbound.rs index 988e651..d6a5d5f 100644 --- a/src/server/packets/serverbound.rs +++ b/src/server/packets/serverbound.rs @@ -162,3 +162,66 @@ impl LoginStart { Ok(()) } } + +#[derive(Debug, Clone)] +pub struct ClientSettings { + pub locale: MCString, + pub view_distance: MCByte, + pub chat_mode: MCVarInt, // 0: enabled, 1: commands only, 2: hidden. + pub chat_colors: MCBoolean, + pub displayed_skin_parts: MCUnsignedByte, // Bit mask + // Displayed skin parts flags: + // Bit 0 (0x01): Cape enabled + // Bit 1 (0x02): Jacket enabled + // Bit 2 (0x04): Left Sleeve enabled + // Bit 3 (0x08): Right Sleeve enabled + // Bit 4 (0x10): Left Pants Leg enabled + // Bit 5 (0x20): Right Pants Leg enabled + // Bit 6 (0x40): Hat enabled +} +impl Into> for ClientSettings { + fn into(self) -> Vec { + let mut out = vec![]; + let mut temp: Vec = MCVarInt::from(0x15).into(); // 0x15 Client Settings. + temp.extend_from_slice(&Into::>::into(self.locale)); + temp.extend_from_slice(&Into::>::into(self.view_distance)); + temp.extend_from_slice(&Into::>::into(self.chat_mode)); + temp.extend_from_slice(&Into::>::into(self.chat_colors)); + temp.extend_from_slice(&Into::>::into(self.displayed_skin_parts)); + out.extend_from_slice(&Into::>::into(MCVarInt::from(temp.len() as i32))); + out.extend_from_slice(&temp); + out + } +} +impl TryFrom> for ClientSettings { + type Error = &'static str; + fn try_from(_bytes: Vec) -> Result { + Err("unimplemented") + } +} +impl ClientSettings { + pub fn new() -> Self { + ClientSettings { + locale: "en_US".into(), + view_distance: 8.into(), // 8 chunks. + chat_mode: 0.into(), // All chat enabled. + chat_colors: true.into(), + displayed_skin_parts: 0xff.into(), // Enable all parts. + } + } + pub async fn read(t: &mut TcpStream) -> tokio::io::Result { + let mut clientsettings = ClientSettings::new(); + clientsettings.locale = MCString::read(t).await?; + clientsettings.view_distance = MCByte::read(t).await?; + clientsettings.chat_mode = MCVarInt::read(t).await?; + clientsettings.chat_colors = MCBoolean::read(t).await?; + clientsettings.displayed_skin_parts = MCUnsignedByte::read(t).await?; + Ok(clientsettings) + } + pub async fn write(&self, t: &mut TcpStream) -> tokio::io::Result<()> { + for b in Into::>::into(self.clone()) { + write_byte(t, b).await?; + } + Ok(()) + } +} diff --git a/src/world/location.rs b/src/world/location.rs new file mode 100644 index 0000000..e283673 --- /dev/null +++ b/src/world/location.rs @@ -0,0 +1,54 @@ +/// Used to store a point in a world. +pub struct Location { + pub x: f64, + pub y: f64, + pub z: f64, + pub pitch: f32, + pub yaw: f32, + // TODO: Store a reference to the world this location is in. +} +impl Location { + /// Create a new `Location`. + pub fn new(x: f64, y: f64, z: f64, pitch: f32, yaw: f32) -> Location { + Location { + x, + y, + z, + pitch, + yaw, + } + } + + /// Create a new `Location` with no rotation. + pub fn position(x: f64, y: f64, z: f64) -> Location { + Location { + x, + y, + z, + pitch: 0.0, + yaw: 0.0, + } + } + + /// Create a new `Location` with no position. + pub fn rotation(pitch: f32, yaw: f32) -> Location { + Location { + x: 0.0, + y: 0.0, + z: 0.0, + pitch, + yaw, + } + } + + /// Create a new `Location` with no rotation or position. + pub fn zero() -> Location { + Location { + x: 0.0, + y: 0.0, + z: 0.0, + pitch: 0.0, + yaw: 0.0, + } + } +} diff --git a/src/world/mod.rs b/src/world/mod.rs index 8b13789..b6c6145 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -1 +1 @@ - +pub mod location;