diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 77c0aa688..0e46802b7 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -1,10 +1,13 @@ +use std::{rc::Rc, sync::Mutex}; use crate::protocol::{ client::{ + config::CFinishConfig, login::{CEncryptionRequest, CLoginSuccess}, status::{CPingResponse, CStatusResponse}, }, server::{ + config::{SAcknowledgeFinishConfig, SClientInformation}, handshake::SHandShake, login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart}, status::{SPingRequest, SStatusRequest}, @@ -12,7 +15,7 @@ use crate::protocol::{ ConnectionState, }; -use super::Client; +use super::{Client, PlayerConfig}; pub trait ClientPacketProcessor { // Handshake @@ -25,6 +28,9 @@ pub trait ClientPacketProcessor { fn handle_encryption_response(&mut self, encryption_response: SEncryptionResponse); fn handle_plugin_response(&mut self, plugin_response: SLoginPluginResponse); fn handle_login_acknowledged(&mut self, login_acknowledged: SLoginAcknowledged); + // Config + fn handle_client_information(&mut self, client_information: SClientInformation); + fn handle_config_acknowledged(&mut self, config_acknowledged: SAcknowledgeFinishConfig); } impl ClientPacketProcessor for Client { @@ -37,9 +43,15 @@ impl ClientPacketProcessor for Client { fn handle_status_request(&mut self, _status_request: SStatusRequest) { dbg!("sending status"); - self.send_packet(CStatusResponse::new( - serde_json::to_string(&self.server.status_response).unwrap(), - )) + dbg!("test first"); + + let guard = self.server.try_lock().unwrap(); + dbg!("test"); + + let response = serde_json::to_string(&guard.status_response).unwrap(); + drop(guard); + + self.send_packet(CStatusResponse::new(response)); } fn handle_ping_request(&mut self, ping_request: SPingRequest) { @@ -53,11 +65,17 @@ impl ClientPacketProcessor for Client { self.name = Some(login_start.name); self.uuid = Some(login_start.uuid); let verify_token: [u8; 4] = rand::random(); - let public_key_der = &self.server.to_owned().public_key_der; + let public_key_der = self + .server + .to_owned() + .lock() + .unwrap() + .public_key_der + .clone(); // todo do not clone let packet = CEncryptionRequest::new( "".into(), public_key_der.len() as i32, - public_key_der, + &public_key_der, verify_token.len() as i32, &verify_token, false, // TODO @@ -85,4 +103,25 @@ impl ClientPacketProcessor for Client { self.connection_state = ConnectionState::Config; dbg!("login achnowlaged"); } + fn handle_client_information(&mut self, client_information: SClientInformation) { + self.config = Some(PlayerConfig { + locale: client_information.locale, + view_distance: client_information.view_distance, + chat_mode: client_information.chat_mode, + chat_colors: client_information.chat_colors, + skin_parts: client_information.skin_parts, + main_hand: client_information.main_hand, + text_filtering: client_information.text_filtering, + server_listing: client_information.server_listing, + }); + // We are done with configuring + self.send_packet(CFinishConfig::new()); + } + + fn handle_config_acknowledged(&mut self, config_acknowledged: SAcknowledgeFinishConfig) { + dbg!("config acknowledged"); + self.connection_state = ConnectionState::Play; + // generate a player + self.server.lock().unwrap().spawn_player(&self.token); + } } diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 9a6322539..bafd8e910 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -2,12 +2,15 @@ use std::{ collections::VecDeque, io::{self, Write}, rc::Rc, + sync::{Arc, Mutex}, }; use crate::{ + entity::player::{ChatMode, Hand}, protocol::{ - client::login::CLoginDisconnect, + client::{config::CConfigDisconnect, login::CLoginDisconnect}, server::{ + config::{SAcknowledgeFinishConfig, SClientInformation}, handshake::SHandShake, login::{SEncryptionResponse, SLoginAcknowledged, SLoginPluginResponse, SLoginStart}, status::{SPingRequest, SStatusRequest}, @@ -19,14 +22,13 @@ use crate::{ use crate::protocol::ConnectionState; use anyhow::Context; -use mio::{event::Event, net::TcpStream, Registry}; +use mio::{event::Event, net::TcpStream, Registry, Token}; use packet_decoder::PacketDecoder; use packet_encoder::PacketEncoder; use rsa::Pkcs1v15Encrypt; use std::io::Read; mod client_packet; -pub mod player; mod packet_decoder; mod packet_encoder; @@ -35,13 +37,27 @@ use client_packet::ClientPacketProcessor; pub const MAX_PACKET_SIZE: i32 = 2097152; +pub struct PlayerConfig { + locale: String, // 16 + view_distance: i8, + chat_mode: ChatMode, + chat_colors: bool, + skin_parts: u8, + main_hand: Hand, + text_filtering: bool, + server_listing: bool, +} + pub struct Client { pub name: Option, pub uuid: Option, - pub server: Rc, + pub config: Option, + + pub server: Arc>, pub connection_state: ConnectionState, pub encrytion: bool, pub closed: bool, + pub token: Rc, pub connection: TcpStream, enc: PacketEncoder, dec: PacketDecoder, @@ -49,11 +65,13 @@ pub struct Client { } impl Client { - pub fn new(server: Rc, connection: TcpStream) -> Self { + pub fn new(server: Arc>, token: Rc, connection: TcpStream) -> Self { Self { name: None, uuid: None, + config: None, server, + token, connection_state: ConnectionState::HandShake, connection, enc: PacketEncoder::default(), @@ -72,6 +90,8 @@ impl Client { self.encrytion = true; let shared_secret = self .server + .lock() + .unwrap() .private_key .decrypt(Pkcs1v15Encrypt, &shared_secret) .context("failed to decrypt shared secret")?; @@ -136,6 +156,18 @@ impl Client { packet.id ), }, + crate::protocol::ConnectionState::Config => match packet.id { + SClientInformation::PACKET_ID => { + self.handle_client_information(SClientInformation::read(bytebuf)) + } + SAcknowledgeFinishConfig::PACKET_ID => { + self.handle_config_acknowledged(SAcknowledgeFinishConfig::read(bytebuf)) + } + _ => log::error!( + "Failed to handle packet id {} while in Config state", + packet.id + ), + }, _ => log::error!("Invalid Connection state {:?}", self.connection_state), } } @@ -184,8 +216,16 @@ impl Client { pub fn kick(&mut self, reason: String) { // Todo - if self.connection_state == ConnectionState::Login { - self.send_packet(CLoginDisconnect::new(reason)); + match self.connection_state { + ConnectionState::Login => { + self.send_packet(CLoginDisconnect::new(reason)); + } + ConnectionState::Config => { + self.send_packet(CConfigDisconnect::new(reason)); + } + _ => { + log::warn!("Cant't kick in {:?} State", self.connection_state) + } } self.close() } diff --git a/pumpkin/src/client/packet_decoder.rs b/pumpkin/src/client/packet_decoder.rs index 74c6e5a38..3c75198c8 100644 --- a/pumpkin/src/client/packet_decoder.rs +++ b/pumpkin/src/client/packet_decoder.rs @@ -1,4 +1,3 @@ - use aes::cipher::{generic_array::GenericArray, BlockDecryptMut, BlockSizeUser, KeyIvInit}; use anyhow::{bail, ensure, Context}; use bytes::{Buf, BytesMut}; diff --git a/pumpkin/src/client/packet_encoder.rs b/pumpkin/src/client/packet_encoder.rs index 68c355045..a50136b8e 100644 --- a/pumpkin/src/client/packet_encoder.rs +++ b/pumpkin/src/client/packet_encoder.rs @@ -37,7 +37,7 @@ impl PacketEncoder { let data_len = self.buf.len() - start_len; if false { // compression - } + } let packet_len = data_len; ensure!( diff --git a/pumpkin/src/client/player.rs b/pumpkin/src/client/player.rs deleted file mode 100644 index 55eea144c..000000000 --- a/pumpkin/src/client/player.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::protocol::RawPacket; - -use super::Client; - -pub struct Player { - // All networking stuff - pub client: Client, -} - -impl Player { - pub fn handle_packet(&mut self, _packet: &mut RawPacket) {} -} diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs new file mode 100644 index 000000000..1255ab620 --- /dev/null +++ b/pumpkin/src/entity/mod.rs @@ -0,0 +1,13 @@ +pub mod player; + +pub type EntityId = i32; + +pub struct Entity { + pub entity_id: EntityId, +} + +impl Entity { + pub fn new(entity_id: EntityId) -> Self { + Self { entity_id } + } +} diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs new file mode 100644 index 000000000..2d93f09f7 --- /dev/null +++ b/pumpkin/src/entity/player.rs @@ -0,0 +1,103 @@ +use std::rc::Rc; + +use crate::{ + client::Client, + protocol::{ClientPacket, RawPacket, VarInt}, +}; + +use super::{Entity, EntityId}; + +pub struct Player { + pub entity: Entity, + // All networking stuff + pub client: Client, +} + +impl Player { + pub fn new(entity: Entity, client: Client) -> Self { + Self { entity, client } + } + + pub fn entity_id(&self) -> EntityId { + self.entity.entity_id + } + + pub fn handle_packet(&mut self, _packet: &mut RawPacket) {} + + pub fn send_packet(&mut self, packet: P) { + self.client.send_packet(packet); + } +} + +pub enum Hand { + Main, + Off, +} + +impl Hand { + pub fn from_varint(varint: VarInt) -> Self { + match varint { + 0 => Self::Off, + 1 => Self::Main, + _ => { + log::info!("Unexpected Hand {}", varint); + Self::Main + } + } + } +} + +pub enum ChatMode { + Enabled, + CommandsOnly, + Hidden, +} + +impl ChatMode { + pub fn from_varint(varint: VarInt) -> Self { + match varint { + 0 => Self::Enabled, + 1 => Self::CommandsOnly, + 2 => Self::Hidden, + _ => { + log::info!("Unexpected ChatMode {}", varint); + Self::Enabled + } + } + } +} + +#[derive(Clone, Copy)] +pub enum GameMode { + Undefined, + Survival, + Creative, + Adventure, + Spectator, +} + +impl GameMode { + pub fn from_byte(byte: i8) -> Self { + match byte { + -1 => GameMode::Undefined, + 0 => GameMode::Survival, + 1 => GameMode::Creative, + 2 => GameMode::Adventure, + 3 => GameMode::Spectator, + _ => { + log::info!("Unexpected GameMode {}", byte); + Self::Survival + } + } + } + + pub fn to_byte(self) -> i8 { + match self { + Self::Undefined => -1, + Self::Survival => 0, + Self::Creative => 1, + Self::Adventure => 2, + Self::Spectator => 3, + } + } +} diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 20760fc6f..0046cfb7a 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -1,5 +1,3 @@ -#![feature(read_buf)] - use mio::net::TcpListener; use mio::{Events, Interest, Poll, Token}; use std::collections::HashMap; @@ -9,13 +7,17 @@ use std::io::{self}; const SERVER: Token = Token(0); pub mod client; +pub mod entity; pub mod protocol; pub mod server; pub mod util; #[cfg(not(target_os = "wasi"))] fn main() -> io::Result<()> { - use std::rc::Rc; + use std::{ + rc::Rc, + sync::{Arc, Mutex}, + }; use client::{interrupted, Client}; use server::Server; @@ -35,14 +37,12 @@ fn main() -> io::Result<()> { poll.registry() .register(&mut listener, SERVER, Interest::READABLE)?; - // Map of `Token` -> `TcpStream`. - let mut connections = HashMap::new(); // Unique token for each incoming connection. let mut unique_token = Token(SERVER.0 + 1); log::info!("You now can connect to the server"); - let server = Rc::new(Server::new()); + let mut server = Arc::new(Mutex::new(Server::new())); loop { if let Err(err) = poll.poll(&mut events, None) { @@ -82,11 +82,14 @@ fn main() -> io::Result<()> { Interest::READABLE.add(Interest::WRITABLE), )?; - connections.insert(token, Client::new(Rc::clone(&server), connection)); + let rc_server = Arc::clone(&server); + let mut guard = server.try_lock().unwrap(); + guard.new_client(rc_server, connection, token); }, + token => { // Maybe received an event for a TCP connection. - let done = if let Some(client) = connections.get_mut(&token) { + let done = if let Some(client) = server.try_lock().unwrap().connections.get_mut(&token) { client.poll(poll.registry(), event).unwrap(); client.closed } else { @@ -94,7 +97,7 @@ fn main() -> io::Result<()> { false }; if done { - if let Some(mut client) = connections.remove(&token) { + if let Some(mut client) = server.try_lock().unwrap().connections.remove(&token) { poll.registry().deregister(&mut client.connection)?; } } diff --git a/pumpkin/src/protocol/bytebuf/buffer.rs b/pumpkin/src/protocol/bytebuf/buffer.rs index 1afa1a458..91570b905 100644 --- a/pumpkin/src/protocol/bytebuf/buffer.rs +++ b/pumpkin/src/protocol/bytebuf/buffer.rs @@ -423,6 +423,12 @@ impl ByteBuffer { self.write_bytes_len(val.as_bytes(), max_len); } + pub fn write_string_array(&mut self, array: &[String]) { + for string in array { + self.write_string(string) + } + } + // Read operations /// Read a defined amount of raw bytes, or return an IO error if not enough bytes are diff --git a/pumpkin/src/protocol/client/config/mod.rs b/pumpkin/src/protocol/client/config/mod.rs new file mode 100644 index 000000000..f79ef36b3 --- /dev/null +++ b/pumpkin/src/protocol/client/config/mod.rs @@ -0,0 +1,43 @@ +use crate::protocol::ClientPacket; + +pub struct CCookieRequest { + // TODO +} + +impl ClientPacket for CCookieRequest { + const PACKET_ID: crate::protocol::VarInt = 0; + + fn write(&self, bytebuf: &mut crate::protocol::bytebuf::buffer::ByteBuffer) {} +} + +pub struct CConfigDisconnect { + reason: String, +} + +impl CConfigDisconnect { + pub fn new(reason: String) -> Self { + Self { reason } + } +} + +impl ClientPacket for CConfigDisconnect { + const PACKET_ID: crate::protocol::VarInt = 2; + + fn write(&self, bytebuf: &mut crate::protocol::bytebuf::buffer::ByteBuffer) { + bytebuf.write_string(&self.reason); + } +} + +pub struct CFinishConfig {} + +impl CFinishConfig { + pub fn new() -> Self { + Self {} + } +} + +impl ClientPacket for CFinishConfig { + const PACKET_ID: crate::protocol::VarInt = 3; + + fn write(&self, _bytebuf: &mut crate::protocol::bytebuf::buffer::ByteBuffer) {} +} diff --git a/pumpkin/src/protocol/client/mod.rs b/pumpkin/src/protocol/client/mod.rs index 5a39e3b3f..27b671550 100644 --- a/pumpkin/src/protocol/client/mod.rs +++ b/pumpkin/src/protocol/client/mod.rs @@ -1,5 +1,7 @@ // Clientbound Packets // Server -> Client // +pub mod config; pub mod login; +pub mod play; pub mod status; diff --git a/pumpkin/src/protocol/client/play/mod.rs b/pumpkin/src/protocol/client/play/mod.rs new file mode 100644 index 000000000..19583aa16 --- /dev/null +++ b/pumpkin/src/protocol/client/play/mod.rs @@ -0,0 +1,110 @@ +use crate::{ + entity::player::GameMode, + protocol::{ClientPacket, VarInt}, +}; + +pub struct CLogin { + entity_id: i32, + is_hardcore: bool, + dimension_count: VarInt, + dimension_names: Vec, + max_players: VarInt, + view_distance: VarInt, + simulated_distance: VarInt, + reduced_debug_info: bool, + enabled_respawn_screen: bool, + limited_crafting: bool, + dimension_type: VarInt, + dimension_name: String, + hashed_seed: i64, + game_mode: GameMode, + previous_gamemode: GameMode, + debug: bool, + is_flat: bool, + has_death_loc: bool, + death_dimension_name: Option, + death_loc: Option, // POSITION NOT STRING + portal_cooldown: VarInt, + enforce_secure_chat: bool, +} + +impl CLogin { + pub fn new( + entity_id: i32, + is_hardcore: bool, + dimension_count: VarInt, + dimension_names: Vec, + max_players: VarInt, + view_distance: VarInt, + simulated_distance: VarInt, + reduced_debug_info: bool, + enabled_respawn_screen: bool, + limited_crafting: bool, + dimension_type: VarInt, + dimension_name: String, + hashed_seed: i64, + game_mode: GameMode, + previous_gamemode: GameMode, + debug: bool, + is_flat: bool, + has_death_loc: bool, + death_dimension_name: Option, + death_loc: Option, + portal_cooldown: VarInt, + enforce_secure_chat: bool, + ) -> Self { + Self { + entity_id, + is_hardcore, + dimension_count, + dimension_names, + max_players, + view_distance, + simulated_distance, + reduced_debug_info, + enabled_respawn_screen, + limited_crafting, + dimension_type, + dimension_name, + hashed_seed, + game_mode, + previous_gamemode, + debug, + is_flat, + has_death_loc, + death_dimension_name, + death_loc, + portal_cooldown, + enforce_secure_chat, + } + } +} + +impl ClientPacket for CLogin { + const PACKET_ID: VarInt = 0x2B; + + fn write(&self, bytebuf: &mut crate::protocol::bytebuf::buffer::ByteBuffer) { + bytebuf.write_i32(self.entity_id); + bytebuf.write_bool(self.is_hardcore); + bytebuf.write_var_int(self.dimension_count); + bytebuf.write_string_array(self.dimension_names.as_slice()); + bytebuf.write_var_int(self.max_players); + bytebuf.write_var_int(self.view_distance); + bytebuf.write_var_int(self.simulated_distance); + bytebuf.write_bool(self.reduced_debug_info); + bytebuf.write_bool(self.enabled_respawn_screen); + bytebuf.write_bool(self.limited_crafting); + bytebuf.write_var_int(self.dimension_type); + bytebuf.write_string(&self.dimension_name); + bytebuf.write_i64(self.hashed_seed); + bytebuf.write_u8(self.game_mode.to_byte() as u8); + bytebuf.write_i8(self.previous_gamemode.to_byte()); + bytebuf.write_bool(self.debug); + bytebuf.write_bool(self.is_flat); + bytebuf.write_bool(self.has_death_loc); + bytebuf.write_option(&self.death_dimension_name, |buf, v| buf.write_string(v)); + bytebuf.write_option(&self.death_loc, |buf, v| buf.write_string(v)); + bytebuf.write_var_int(self.portal_cooldown); + bytebuf.write_bool(self.enforce_secure_chat); + } +} diff --git a/pumpkin/src/protocol/mod.rs b/pumpkin/src/protocol/mod.rs index 9e25c8c3c..1021949cf 100644 --- a/pumpkin/src/protocol/mod.rs +++ b/pumpkin/src/protocol/mod.rs @@ -91,9 +91,10 @@ pub type VarLong = i64; pub enum ConnectionState { HandShake, Status, - Login, + Login, Transfer, Config, + Play, } impl ConnectionState { @@ -102,7 +103,10 @@ impl ConnectionState { 1 => Self::Status, 2 => Self::Login, 3 => Self::Transfer, - _ => panic!("Unexpected Status {}", var_int), + _ => { + log::info!("Unexpected Status {}", var_int); + Self::Status + } } } } @@ -138,7 +142,7 @@ pub struct Version { pub struct Players { pub max: u32, pub online: u32, - pub sample: Sample, + pub sample: Vec, } #[derive(Serialize, Deserialize)] diff --git a/pumpkin/src/protocol/server/config/mod.rs b/pumpkin/src/protocol/server/config/mod.rs new file mode 100644 index 000000000..b3a537a97 --- /dev/null +++ b/pumpkin/src/protocol/server/config/mod.rs @@ -0,0 +1,42 @@ +use crate::{ + entity::player::{ChatMode, Hand}, + protocol::{bytebuf::buffer::ByteBuffer, VarInt}, +}; + +pub struct SClientInformation { + pub locale: String, // 16 + pub view_distance: i8, + pub chat_mode: ChatMode, // Varint + pub chat_colors: bool, + pub skin_parts: u8, + pub main_hand: Hand, + pub text_filtering: bool, + pub server_listing: bool, +} + +impl SClientInformation { + pub const PACKET_ID: VarInt = 0; + + pub fn read(bytebuf: &mut ByteBuffer) -> Self { + Self { + locale: bytebuf.read_string_len(16).unwrap(), + view_distance: bytebuf.read_i8().unwrap(), + chat_mode: ChatMode::from_varint(bytebuf.read_var_int().unwrap()), + chat_colors: bytebuf.read_bool().unwrap(), + skin_parts: bytebuf.read_u8().unwrap(), + main_hand: Hand::from_varint(bytebuf.read_var_int().unwrap()), + text_filtering: bytebuf.read_bool().unwrap(), + server_listing: bytebuf.read_bool().unwrap(), + } + } +} + +pub struct SAcknowledgeFinishConfig {} + +impl SAcknowledgeFinishConfig { + pub const PACKET_ID: VarInt = 3; + + pub fn read(_bytebuf: &mut ByteBuffer) -> Self { + Self {} + } +} diff --git a/pumpkin/src/protocol/server/mod.rs b/pumpkin/src/protocol/server/mod.rs index e08af9d63..4ff0995f9 100644 --- a/pumpkin/src/protocol/server/mod.rs +++ b/pumpkin/src/protocol/server/mod.rs @@ -1,5 +1,6 @@ // Serverbound Packets // Client -> Server +pub mod config; pub mod handshake; pub mod login; pub mod status; diff --git a/pumpkin/src/protocol/server/play/mod.rs b/pumpkin/src/protocol/server/play/mod.rs new file mode 100644 index 000000000..e69de29bb diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index 4f6f0da97..99b2aa537 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -1,20 +1,44 @@ -use std::io::Cursor; +use std::{ + collections::HashMap, + io::Cursor, + rc::Rc, + sync::{ + atomic::{AtomicI32, Ordering}, + Arc, Mutex, + }, +}; use base64::{engine::general_purpose, Engine}; +use mio::{net::TcpStream, Token}; use rsa::{rand_core::OsRng, traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey}; -use crate::protocol::{Players, Sample, StatusResponse, Version}; +use crate::{ + client::Client, + entity::{ + player::{GameMode, Player}, + Entity, EntityId, + }, + protocol::{client::play::CLogin, Players, Sample, StatusResponse, VarInt, Version}, +}; pub struct Server { pub compression_threshold: Option, pub online_mode: bool, - pub encriyption: bool, // encription is always required when online_mode is disabled + pub encryption: bool, // encryptiony is always required when online_mode is disabled pub public_key: RsaPublicKey, pub private_key: RsaPrivateKey, pub public_key_der: Box<[u8]>, + pub max_players: u32, + pub status_response: StatusResponse, + pub connections: HashMap, + + // todo replace with HashMap + entity_id: AtomicI32, // todo: place this into every world + pub players: Vec, + pub difficulty: Difficulty, } impl Default for Server { @@ -25,7 +49,8 @@ impl Default for Server { impl Server { pub fn new() -> Self { - let status_response = Self::default_response(); + let max_players = 20; + let status_response = Self::default_response(max_players); // todo, only create when needed let (public_key, private_key) = Self::generate_keys(); @@ -37,17 +62,66 @@ impl Server { .into_boxed_slice(); Self { + // 0 is invalid + entity_id: 2.into(), online_mode: true, - encriyption: true, + encryption: true, compression_threshold: None, // 256 public_key, private_key, + max_players, status_response, public_key_der, + connections: HashMap::new(), + players: Vec::new(), + difficulty: Difficulty::Normal, } } - pub fn default_response() -> StatusResponse { + pub fn new_client(&mut self, rc: Arc>, connection: TcpStream, token: Token) { + self.connections + .insert(token, Client::new(rc, Rc::new(token), connection)); + } + + pub fn spawn_player(&mut self, token: &Token) { + let mut player = Player { + entity: Entity::new(self.new_entity_id()), + client: self.connections.remove(token).unwrap(), + }; + player.send_packet(CLogin::new( + player.entity_id(), + self.difficulty == Difficulty::Hard, + 1, + vec!["minecraft:overworld".into()], + self.max_players as VarInt, + 8, // view distance todo + 8, // sim view dinstance todo + false, + false, + false, + 1, + "minecraft:overworld".into(), + 0, // seed + GameMode::Survival, + GameMode::Undefined, + false, + false, + false, // deth loc + None, + None, + 0, + false, + )); + + self.players.push(player); + } + + // move to world + pub fn new_entity_id(&self) -> EntityId { + self.entity_id.fetch_add(1, Ordering::SeqCst) + } + + pub fn default_response(max_players: u32) -> StatusResponse { let path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png"); StatusResponse { @@ -56,12 +130,12 @@ impl Server { protocol: 767, }, players: Players { - max: 20, + max: max_players, online: 0, - sample: Sample { + sample: vec![Sample { name: "".into(), id: "".into(), - }, + }], }, description: "Pumpkin Server".into(), favicon: Self::load_icon(path), @@ -88,3 +162,10 @@ impl Server { (pub_key, priv_key) } } + +#[derive(PartialEq)] +pub enum Difficulty { + Easy, + Normal, + Hard, +}