diff --git a/pumpkin/src/client/client_packet.rs b/pumpkin/src/client/client_packet.rs index 63bc891dd..8f347ee98 100644 --- a/pumpkin/src/client/client_packet.rs +++ b/pumpkin/src/client/client_packet.rs @@ -5,6 +5,7 @@ use crate::{ login::{CEncryptionRequest, CLoginSuccess}, status::{CPingResponse, CStatusResponse}, }, + registry::{DimensionCodec, WolfCodec}, server::{ config::{SAcknowledgeFinishConfig, SClientInformation, SKnownPacks}, handshake::SHandShake, @@ -67,12 +68,8 @@ impl ClientPacketProcessor for Client { fn handle_status_request(&mut self, server: &mut Server, _status_request: SStatusRequest) { dbg!("sending status"); - if let Ok(response) = serde_json::to_string(&server.status_response) { - self.send_packet(CStatusResponse::new(response)) - .unwrap_or_else(|e| self.kick(&e.to_string())); - } else { - log::error!("Failed to parse Status response to JSON") - } + self.send_packet(CStatusResponse::new(&server.status_response_json)) + .unwrap_or_else(|e| self.kick(&e.to_string())); } fn handle_ping_request(&mut self, _server: &mut Server, ping_request: SPingRequest) { @@ -129,11 +126,13 @@ impl ClientPacketProcessor for Client { fn handle_login_acknowledged( &mut self, - _server: &mut Server, + server: &mut Server, _login_acknowledged: SLoginAcknowledged, ) { self.connection_state = ConnectionState::Config; - Server::send_brand(self).unwrap_or_else(|e| self.kick(&e.to_string())); + server + .send_brand(self) + .unwrap_or_else(|e| self.kick(&e.to_string())); // known data packs self.send_packet(CKnownPacks::new(&[KnownPack { namespace: "minecraft", @@ -167,6 +166,12 @@ impl ClientPacketProcessor for Client { Entry { entry_id: "minecraft:dimension_type", has_data: true, + data: &DimensionCodec::parse(), + }, + Entry { + entry_id: "minecraft:wolf_variant", + has_data: true, + data: &WolfCodec::parse(), }, /* Entry { entry_id: "minecraft:worldgen/biome".into(), diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 1d2a5c538..8cad8eb63 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -83,10 +83,12 @@ impl Client { } } + /// adds a Incoming packet to the queue pub fn add_packet(&mut self, packet: RawPacket) { self.client_packets_queue.push_back(packet); } + /// enables encryption pub fn enable_encryption( &mut self, server: &mut Server, @@ -126,6 +128,7 @@ impl Client { } } + /// Handles an incoming decoded Packet pub fn handle_packet(&mut self, server: &mut Server, packet: &mut RawPacket) { dbg!("Handling packet"); let bytebuf = &mut packet.bytebuf; @@ -186,6 +189,7 @@ impl Client { } } + // Reads the connection until our buffer of len 4096 is full, then decode /// Returns `true` if the connection is closed. pub fn poll(&mut self, server: &mut Server, event: &Event) -> Result { if event.is_readable() { @@ -233,6 +237,7 @@ impl Client { Ok(self.closed) } + /// Kicks the Client with a reason depending on the connection state pub fn kick(&mut self, reason: &str) { // Todo match self.connection_state { @@ -251,7 +256,7 @@ impl Client { self.close() } - // Kick before when needed + /// You should prefer to use `kick` when you can pub fn close(&mut self) { self.closed = true; } diff --git a/pumpkin/src/protocol/client/config/mod.rs b/pumpkin/src/protocol/client/config/mod.rs index aa4e72099..da1335b90 100644 --- a/pumpkin/src/protocol/client/config/mod.rs +++ b/pumpkin/src/protocol/client/config/mod.rs @@ -1,4 +1,4 @@ -use crate::protocol::{bytebuf::ByteBuffer, registry, ClientPacket, KnownPack, VarInt}; +use crate::protocol::{bytebuf::ByteBuffer, ClientPacket, KnownPack, VarInt}; pub struct CCookieRequest { // TODO @@ -11,12 +11,12 @@ impl ClientPacket for CCookieRequest { } pub struct CPluginMessage<'a> { - channel: String, + channel: &'a str, data: &'a [u8], } impl<'a> CPluginMessage<'a> { - pub fn new(channel: String, data: &'a [u8]) -> Self { + pub fn new(channel: &'a str, data: &'a [u8]) -> Self { Self { channel, data } } } @@ -25,7 +25,7 @@ impl<'a> ClientPacket for CPluginMessage<'a> { const PACKET_ID: VarInt = 0x01; fn write(&self, bytebuf: &mut ByteBuffer) { - bytebuf.put_string(&self.channel); + bytebuf.put_string(self.channel); bytebuf.put_slice(self.data); } } @@ -107,7 +107,7 @@ impl<'a> CRegistryData<'a> { pub struct Entry<'a> { pub entry_id: &'a str, pub has_data: bool, - // data provided by registry::write_codec + pub data: &'a [u8], } impl<'a> ClientPacket for CRegistryData<'a> { @@ -118,7 +118,7 @@ impl<'a> ClientPacket for CRegistryData<'a> { bytebuf.put_list::(self.entries, |p, v| { p.put_string(v.entry_id); p.put_bool(v.has_data); - registry::write_codec(p, -64, 384); + p.put_slice(v.data); }); } } diff --git a/pumpkin/src/protocol/client/status/mod.rs b/pumpkin/src/protocol/client/status/mod.rs index 32a5eb9a7..414210371 100644 --- a/pumpkin/src/protocol/client/status/mod.rs +++ b/pumpkin/src/protocol/client/status/mod.rs @@ -18,20 +18,20 @@ impl ClientPacket for CPingResponse { } } -pub struct CStatusResponse { - json_response: String, // 32767 +pub struct CStatusResponse<'a> { + json_response: &'a str, // 32767 } -impl CStatusResponse { - pub fn new(json_response: String) -> Self { +impl<'a> CStatusResponse<'a> { + pub fn new(json_response: &'a str) -> Self { Self { json_response } } } -impl ClientPacket for CStatusResponse { +impl<'a> ClientPacket for CStatusResponse<'a> { const PACKET_ID: VarInt = 0x00; fn write(&self, bytebuf: &mut ByteBuffer) { - bytebuf.put_string(self.json_response.as_str()); + bytebuf.put_string(self.json_response); } } diff --git a/pumpkin/src/protocol/mod.rs b/pumpkin/src/protocol/mod.rs index 13283435a..d38597ae3 100644 --- a/pumpkin/src/protocol/mod.rs +++ b/pumpkin/src/protocol/mod.rs @@ -6,7 +6,7 @@ use serde::Serialize; use thiserror::Error; pub mod bytebuf; -mod registry; +pub mod registry; pub mod client; pub mod server; diff --git a/pumpkin/src/protocol/registry/dimensions.rs b/pumpkin/src/protocol/registry/dimensions.rs index 7264790a2..265d70ab4 100644 --- a/pumpkin/src/protocol/registry/dimensions.rs +++ b/pumpkin/src/protocol/registry/dimensions.rs @@ -2,25 +2,25 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Dimension { - pub ambient_light: f32, - bed_works: i32, + ambient_light: f32, + bed_works: u8, coordinate_scale: f64, effects: DimensionEffects, #[serde(skip_serializing_if = "Option::is_none")] - fixed_time: Option, - has_ceiling: i32, - has_raids: i32, - has_skylight: i32, + fixed_time: Option, + has_ceiling: u8, + has_raids: u8, + has_skylight: u8, height: i32, infiniburn: String, logical_height: i32, min_y: i32, monster_spawn_block_light_limit: i32, monster_spawn_light_level: MonsterSpawnLightLevel, - natural: i32, - piglin_safe: i32, - respawn_anchor_works: i32, - ultrawarm: i32, + natural: u8, + piglin_safe: u8, + respawn_anchor_works: u8, + ultrawarm: u8, } #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Debug)] @@ -50,7 +50,12 @@ impl Default for Dimension { logical_height: 384, min_y: -64, monster_spawn_block_light_limit: 0, - monster_spawn_light_level: MonsterSpawnLightLevel::Int(7), + monster_spawn_light_level: MonsterSpawnLightLevel::Tagged( + MonsterSpawnLightLevelTagged::Uniform { + min_inclusive: 7, + max_inclusive: 0, + }, + ), natural: 1, piglin_safe: 0, respawn_anchor_works: 0, diff --git a/pumpkin/src/protocol/registry/mod.rs b/pumpkin/src/protocol/registry/mod.rs index a17e04264..aa5da1251 100644 --- a/pumpkin/src/protocol/registry/mod.rs +++ b/pumpkin/src/protocol/registry/mod.rs @@ -1,10 +1,7 @@ -use core::str; -use std::{fs::File, io::Read}; - -use dimensions::{Dimension, DimensionEffects, MonsterSpawnLightLevel}; -use fastnbt::{nbt, to_bytes_with_opts, SerOpts}; -use flate2::read::GzDecoder; +use dimensions::Dimension; +use fastnbt::SerOpts; use serde::Serialize; +use wolf::WolfVariant; use super::bytebuf::ByteBuffer; @@ -12,11 +9,14 @@ mod biomes; mod chat_type; mod damage_type; mod dimensions; +mod wolf; #[derive(Debug, Clone, Serialize)] struct LoginInfo { #[serde(rename = "minecraft:dimension_type")] dimensions: Codec, + // #[serde(rename = "minecraft:wolf_variant")] + // wolf: Codec, /* #[serde(rename = "minecraft:worldgen/biome")] biomes: Codec, #[serde(rename = "minecraft:chat_type")] @@ -25,6 +25,52 @@ struct LoginInfo { damage: Codec, */ } +#[derive(Debug, Clone, Serialize)] +pub struct DimensionCodec { + #[serde(rename = "minecraft:dimension_type")] + dimensions: Codec, +} + +impl DimensionCodec { + pub fn parse() -> Vec { + let dimension = Dimension::default(); + let codec = DimensionCodec { + dimensions: Codec { + ty: "minecraft:dimension_type".into(), + value: vec![RegistryValue { + name: "minecraft:overworld".into(), + id: 0, + element: dimension, + }], + }, + }; + fastnbt::to_bytes_with_opts(&codec, SerOpts::network_nbt()).unwrap() + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct WolfCodec { + #[serde(rename = "minecraft:wolf_variant")] + wolf: Codec, +} + +impl WolfCodec { + pub fn parse() -> Vec { + let wolf = WolfVariant::default(); + let codec = WolfCodec { + wolf: Codec { + ty: "minecraft:wolf_variant".into(), + value: vec![RegistryValue { + name: "minecraft:wolf_variant".into(), + id: 0, + element: wolf, + }], + }, + }; + fastnbt::to_bytes_with_opts(&codec, SerOpts::network_nbt()).unwrap() + } +} + #[derive(Debug, Clone, Serialize)] struct Codec { #[serde(rename = "type")] @@ -33,13 +79,14 @@ struct Codec { } #[derive(Debug, Clone, Serialize)] struct RegistryValue { - id: i32, name: String, + id: i32, element: T, } -pub fn write_codec(out: &mut ByteBuffer, world_min_y: i32, world_height: u32) { +pub fn write_codec(out: &mut ByteBuffer, world_min_y: i32, world_height: u32) -> Vec { let dimension = Dimension::default(); + let wolf = WolfVariant::default(); let info = LoginInfo { dimensions: Codec { @@ -50,7 +97,14 @@ pub fn write_codec(out: &mut ByteBuffer, world_min_y: i32, world_height: u32) { element: dimension, }], }, - /* biomes: Codec { + /* wolf: Codec { + ty: "minecraft:wolf_variant".into(), + value: vec![RegistryValue { + name: "minecraft:wolf_variant".into(), + id: 0, + element: wolf, + }], + }, */ /* biomes: Codec { ty: "minecraft:worldgen/biome".into(), value: biomes::all(), }, @@ -64,8 +118,5 @@ pub fn write_codec(out: &mut ByteBuffer, world_min_y: i32, world_height: u32) { }, */ }; - let val = &fastnbt::to_bytes_with_opts(&info, SerOpts::network_nbt()).unwrap(); - dbg!(val); - // Dimension codec - out.put_slice(val); + fastnbt::to_bytes_with_opts(&info, SerOpts::network_nbt()).unwrap() } diff --git a/pumpkin/src/protocol/registry/wolf.rs b/pumpkin/src/protocol/registry/wolf.rs new file mode 100644 index 000000000..85b0e9e3a --- /dev/null +++ b/pumpkin/src/protocol/registry/wolf.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WolfVariant { + wild_texture: String, + tame_texture: String, + angry_texture: String, + biomes: String, +} + +impl Default for WolfVariant { + fn default() -> Self { + Self { + wild_texture: "minecraft:entity/wolf/wolf_ashen".to_string(), + tame_texture: "minecraft:entity/wolf/wolf_ashen_tame".to_string(), + angry_texture: "minecraft:entity/wolf/wolf_ashen_angry".to_string(), + biomes: "minecraft:snowy_taiga".to_string(), + } + } +} diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index 76602c4fc..cb7235dff 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -31,11 +31,18 @@ pub struct Server { pub private_key: RsaPrivateKey, pub public_key_der: Box<[u8]>, + /// the maximum amount of players that can join the Server pub max_players: u32, pub world: World, pub status_response: StatusResponse, + // We cache the json response here so we don't parse it every time someone makes a Status request. + // Keep in mind that we must parse this again, when the StatusResponse changes which usally happen when a player joins or leaves + pub status_response_json: String, + + /// Cache the Server brand buffer so we don't have to rebuild them every time a player joins + pub cached_server_brand: Vec, // todo replace with HashMap entity_id: AtomicI32, // todo: place this into every world @@ -44,9 +51,11 @@ pub struct Server { impl Server { pub fn new(config: (BasicConfiguration, AdvancedConfiguration)) -> Self { - let max_players = 20; - let config_clone = &config; - let status_response = Self::default_response(config_clone); + let status_response = Self::build_response(&config.0); + let status_response_json = serde_json::to_string(&status_response) + .expect("Failed to parse Status response into JSON"); + + let cached_server_brand = Self::build_brand(); // todo, only create when needed let (public_key, private_key) = Self::generate_keys(); @@ -65,9 +74,11 @@ impl Server { encryption: config.1.encryption, compression_threshold: None, // 256 public_key, + cached_server_brand, private_key, - max_players, + max_players: config.0.max_plyers, status_response, + status_response_json, public_key_der, difficulty: config.0.default_difficulty, } @@ -127,21 +138,23 @@ impl Server { self.entity_id.fetch_add(1, Ordering::SeqCst) } - pub fn send_brand(client: &mut Client) -> Result<(), PacketError> { - // send server brand + pub fn build_brand() -> Vec { let brand = "pumpkin"; let mut buf = vec![]; let _ = VarInt32(brand.len() as i32).encode(&mut buf); buf.extend_from_slice(brand.as_bytes()); + buf + } + + pub fn send_brand(&self, client: &mut Client) -> Result<(), PacketError> { + // send server brand client.send_packet(CPluginMessage::new( - "minecraft:brand".to_string(), - buf.as_slice(), + "minecraft:brand", + &self.cached_server_brand, )) } - pub fn default_response( - config: &(BasicConfiguration, AdvancedConfiguration), - ) -> StatusResponse { + pub fn build_response(config: &BasicConfiguration) -> StatusResponse { let path = concat!(env!("CARGO_MANIFEST_DIR"), "/icon.png"); StatusResponse { @@ -150,14 +163,14 @@ impl Server { protocol: 767, }, players: Players { - max: config.0.max_plyers, + max: config.max_plyers, online: 0, sample: vec![Sample { name: "".into(), id: "".into(), }], }, - description: config.0.motd.clone(), + description: config.motd.clone(), favicon: Self::load_icon(path), } }