Join Game and Client Settings

This commit is contained in:
Garen Tyler 2021-03-02 12:05:09 -07:00
parent 4b4c962f7b
commit 9def1e2def
7 changed files with 222 additions and 10 deletions

View File

@ -1 +1 @@
pub mod player;

8
src/entity/player.rs Normal file
View File

@ -0,0 +1,8 @@
use crate::world::location::Location;
use crate::server::NetworkClient;
pub struct Player {
position: Location,
display_name: String,
connection: NetworkClient,
}

View File

@ -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<NetworkClient>,
network_clients: Vec<NetworkClient>,
network_receiver: Receiver<NetworkClient>,
pub players: Vec<Player>,
}
impl Server {
pub fn new<A: 'static + ToSocketAddrs + Send>(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<String>,
pub username: Option<String>,
}
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 => {

View File

@ -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<Vec<u8>> for JoinGame {
fn into(self) -> Vec<u8> {
let mut out = vec![];
let mut temp: Vec<u8> = MCVarInt::from(0x01).into(); // 0x01 Join Game.
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.entity_id));
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.gamemode));
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.dimension));
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.difficulty));
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.max_players));
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.level_type));
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.reduced_debug_info));
out.extend_from_slice(&Into::<Vec<u8>>::into(MCVarInt::from(temp.len() as i32)));
out.extend_from_slice(&temp);
out
}
}
impl TryFrom<Vec<u8>> for JoinGame {
type Error = &'static str;
fn try_from(_bytes: Vec<u8>) -> Result<Self, Self::Error> {
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<Self> {
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::<Vec<u8>>::into(self.clone()) {
write_byte(t, b).await?;
}
Ok(())
}
}

View File

@ -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<Vec<u8>> for ClientSettings {
fn into(self) -> Vec<u8> {
let mut out = vec![];
let mut temp: Vec<u8> = MCVarInt::from(0x15).into(); // 0x15 Client Settings.
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.locale));
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.view_distance));
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.chat_mode));
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.chat_colors));
temp.extend_from_slice(&Into::<Vec<u8>>::into(self.displayed_skin_parts));
out.extend_from_slice(&Into::<Vec<u8>>::into(MCVarInt::from(temp.len() as i32)));
out.extend_from_slice(&temp);
out
}
}
impl TryFrom<Vec<u8>> for ClientSettings {
type Error = &'static str;
fn try_from(_bytes: Vec<u8>) -> Result<Self, Self::Error> {
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<Self> {
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::<Vec<u8>>::into(self.clone()) {
write_byte(t, b).await?;
}
Ok(())
}
}

54
src/world/location.rs Normal file
View File

@ -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,
}
}
}

View File

@ -1 +1 @@
pub mod location;