From 13582005c4b47f879eb94a68c36af480e9678bc0 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:52:51 +0000 Subject: [PATCH 01/15] feat: Velocity Support --- .etc/example-config.toml | 6 ++ Cargo.toml | 3 + src/bin/Cargo.toml | 5 +- src/bin/src/events.rs | 21 ++++ src/bin/src/main.rs | 27 +++++ src/bin/src/packet_handlers/login_process.rs | 45 ++++---- src/bin/src/systems/ticking_system.rs | 4 +- src/bin/src/velocity.rs | 102 ++++++++++++++++++ src/lib/core/Cargo.toml | 8 +- src/lib/core/src/identity/player_identity.rs | 88 ++++++++++++++- src/lib/derive_macros/src/events/mod.rs | 1 + src/lib/events/src/errors.rs | 4 + src/lib/events/src/infrastructure.rs | 16 +-- src/lib/net/Cargo.toml | 1 + .../net/crates/codec/src/decode/primitives.rs | 6 ++ .../net/crates/codec/src/encode/primitives.rs | 10 ++ .../src/net_types/length_prefixed_vec.rs | 27 +++-- .../net/crates/codec/src/net_types/var_int.rs | 2 +- src/lib/net/src/connection.rs | 30 ++++-- src/lib/net/src/errors.rs | 9 ++ .../incoming/server_bound_plugin_message.rs | 79 +++++++++++--- .../outgoing/client_bound_plugin_message.rs | 74 +++++++++++++ .../net/src/packets/outgoing/disconnect.rs | 50 +++++++-- .../net/src/packets/outgoing/login_success.rs | 16 ++- src/lib/net/src/packets/outgoing/mod.rs | 2 + .../packets/outgoing/player_info_update.rs | 95 ++++++++++++++++ src/lib/net/src/utils/state.rs | 10 +- src/lib/utils/config/src/server_config.rs | 15 +++ 28 files changed, 664 insertions(+), 92 deletions(-) create mode 100644 src/bin/src/events.rs create mode 100644 src/bin/src/velocity.rs create mode 100644 src/lib/net/src/packets/outgoing/client_bound_plugin_message.rs create mode 100644 src/lib/net/src/packets/outgoing/player_info_update.rs diff --git a/.etc/example-config.toml b/.etc/example-config.toml index 50b32f9e..be149f0e 100644 --- a/.etc/example-config.toml +++ b/.etc/example-config.toml @@ -34,3 +34,9 @@ map_size = 1_000 cache_ttl = 60 # How big the cache can be in kb. cache_capacity = 20_000 + +# Velocity configuration +[velocity] +enabled = false +# The key from forwarding.secret +secret = "" diff --git a/Cargo.toml b/Cargo.toml index b79853e4..15a2f11a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,6 +128,8 @@ thiserror = "2.0.3" rand = "0.9.0-beta.0" fnv = "1.0.7" wyhash = "0.5.0" +sha2 = "=0.10.8" +hmac = "0.12.1" # Encoding/Serialization serde = { version = "1.0.210", features = ["derive"] } @@ -136,6 +138,7 @@ serde_derive = "1.0.210" base64 = "0.22.1" bitcode = "0.6.3" bitcode_derive = "0.6.3" +bitmask-enum = "2.2.5" # Bit manipulation byteorder = "1.5.0" diff --git a/src/bin/Cargo.toml b/src/bin/Cargo.toml index 803db5a7..87d0ddb3 100644 --- a/src/bin/Cargo.toml +++ b/src/bin/Cargo.toml @@ -26,7 +26,9 @@ ferrumc-macros = { workspace = true } ferrumc-nbt = { workspace = true } ferrumc-general-purpose = { workspace = true } ferrumc-state = { workspace = true } +ferrumc-text = { workspace = true } +rand = { workspace = true } ctor = { workspace = true } parking_lot = { workspace = true } tracing = { workspace = true } @@ -36,7 +38,8 @@ futures = { workspace = true } serde_json = { workspace = true } async-trait = { workspace = true } clap = { workspace = true, features = ["derive"] } - +hmac = { workspace = true } +sha2 = { workspace = true } [[bin]] name = "ferrumc" diff --git a/src/bin/src/events.rs b/src/bin/src/events.rs new file mode 100644 index 00000000..2f802395 --- /dev/null +++ b/src/bin/src/events.rs @@ -0,0 +1,21 @@ +use ferrumc_core::identity::player_identity::*; +use ferrumc_macros::Event; +use ferrumc_ecs::entities::Entity; + +/// This event is triggered when the player attempts to log on to the server. +/// +/// Beware that not all components on the entity may be set yet this event is mostly for: +/// a custom handshaking protocol before the player logs in using login plugin messages/etc. +/// +#[derive(Event, Clone)] +pub struct PlayerStartLoginEvent { + /// The entity that this event was fired for. + pub entity: Entity, + + /// This profile can be changed and after the event is finished this will be the new profile. + /// + /// Be warned that this event can be cancelled or this field can be overriden by other listeners and + /// this could mean your profile will never be used. + /// + pub profile: PlayerIdentity, +} diff --git a/src/bin/src/main.rs b/src/bin/src/main.rs index bbd8fdd9..f80ef1d0 100644 --- a/src/bin/src/main.rs +++ b/src/bin/src/main.rs @@ -13,15 +13,42 @@ use ferrumc_world::World; use std::sync::Arc; use systems::definition; use tracing::{error, info}; +use ferrumc_core::identity::player_identity::PlayerIdentity; +use ferrumc_net::packets::outgoing::login_success::LoginSuccessPacket; +use ferrumc_net::{connection::StreamWriter, NetResult}; +use ferrumc_net_codec::encode::NetEncodeOpts; pub(crate) mod errors; use crate::cli::{CLIArgs, Command, ImportArgs}; mod cli; mod packet_handlers; mod systems; +pub mod events; +mod velocity; pub type Result = std::result::Result; +pub async fn send_login_success(state: Arc, conn_id: usize, identity: PlayerIdentity) -> NetResult<()> { + //Send a Login Success Response to further the login sequence + let mut writer = state + .universe + .get_mut::(conn_id)?; + + writer + .send_packet( + &LoginSuccessPacket::new(identity.clone()), + &NetEncodeOpts::WithLength, + ) + .await?; + + state.universe.add_component::( + conn_id, + identity, + )?; + + Ok(()) +} + #[tokio::main] async fn main() { let cli_args = CLIArgs::parse(); diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index c26609aa..960e73be 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -16,7 +16,6 @@ use ferrumc_net::packets::outgoing::finish_configuration::FinishConfigurationPac use ferrumc_net::packets::outgoing::game_event::GameEventPacket; use ferrumc_net::packets::outgoing::keep_alive::OutgoingKeepAlivePacket; use ferrumc_net::packets::outgoing::login_play::LoginPlayPacket; -use ferrumc_net::packets::outgoing::login_success::LoginSuccessPacket; use ferrumc_net::packets::outgoing::registry_data::get_registry_packets; use ferrumc_net::packets::outgoing::set_center_chunk::SetCenterChunk; use ferrumc_net::packets::outgoing::set_default_spawn_position::SetDefaultSpawnPositionPacket; @@ -25,6 +24,10 @@ use ferrumc_net::packets::outgoing::synchronize_player_position::SynchronizePlay use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_state::GlobalState; use tracing::{debug, trace}; +use ferrumc_net::packets::outgoing::player_info_update::{PlayerInfoUpdatePacket, PlayerInfo}; +use crate::events::PlayerStartLoginEvent; +use ferrumc_events::errors::EventsError; +use ferrumc_events::infrastructure::Event; #[event_handler] async fn handle_login_start( @@ -38,24 +41,20 @@ async fn handle_login_start( debug!("Received login start from user with username {}", username); // Add the player identity component to the ECS for the entity. - state.universe.add_component::( - login_start_event.conn_id, - PlayerIdentity::new(username.to_string(), uuid), - )?; - - //Send a Login Success Response to further the login sequence - let mut writer = state - .universe - .get_mut::(login_start_event.conn_id)?; - - writer - .send_packet( - &LoginSuccessPacket::new(uuid, username), - &NetEncodeOpts::WithLength, - ) - .await?; - - Ok(login_start_event) + let event = PlayerStartLoginEvent { + entity: login_start_event.conn_id, + profile: PlayerIdentity::new(username.to_string(), uuid), + }; + + match PlayerStartLoginEvent::trigger(event, state.clone()).await { + Err(NetError::Kick(msg)) => Err(NetError::Kick(msg)), + Err(NetError::EventsError(EventsError::Cancelled)) => Ok(login_start_event), + Ok(event) => { + crate::send_login_success(state, login_start_event.conn_id, event.profile).await?; + Ok(login_start_event) + }, + e => e.map(|_| login_start_event), + } } #[event_handler] @@ -168,6 +167,14 @@ async fn handle_ack_finish_configuration( &NetEncodeOpts::WithLength, ) .await?; + + let profile = state + .universe + .get::(ack_finish_configuration_event.conn_id)?; + writer.send_packet(&PlayerInfoUpdatePacket::new(vec![ + PlayerInfo::from(&profile) + ]), &NetEncodeOpts::WithLength).await?; + send_keep_alive(conn_id, state, &mut writer).await?; Ok(ack_finish_configuration_event) diff --git a/src/bin/src/systems/ticking_system.rs b/src/bin/src/systems/ticking_system.rs index ffe6f21f..645f2c41 100644 --- a/src/bin/src/systems/ticking_system.rs +++ b/src/bin/src/systems/ticking_system.rs @@ -21,8 +21,8 @@ impl System for TickingSystem { let required_end = Instant::now() + Duration::from_millis(50); // TODO handle error let res = TickEvent::trigger(TickEvent::new(tick), state.clone()).await; - if res.is_err() { - debug!("error handling tick event: {:?}", res); + if let Err(e) = res { + debug!("error handling tick event: {:?}", e); } let now = Instant::now(); if required_end > now { diff --git a/src/bin/src/velocity.rs b/src/bin/src/velocity.rs new file mode 100644 index 00000000..5d143100 --- /dev/null +++ b/src/bin/src/velocity.rs @@ -0,0 +1,102 @@ +use std::io::Cursor; +use crate::events::*; +use ferrumc_state::GlobalState; +use ferrumc_net::packets::outgoing::client_bound_plugin_message::*; +use ferrumc_net::packets::incoming::server_bound_plugin_message::*; +use ferrumc_net::{connection::StreamWriter, errors::NetError, NetResult}; +use ferrumc_events::errors::EventsError; +use ferrumc_macros::event_handler; +use ferrumc_net_codec::decode::NetDecode; +use ferrumc_text::*; +use ferrumc_net::utils::ecs_helpers::EntityExt; +use ferrumc_net_codec::{encode::NetEncodeOpts, decode::NetDecodeOpts, net_types::var_int::VarInt}; +use ferrumc_config::statics::get_global_config; +use ferrumc_core::identity::player_identity::PlayerIdentity; +use tokio::io::AsyncReadExt; +use sha2::Sha256; +use hmac::{Hmac, Mac}; + +type HmacSha256 = Hmac; + +struct VelocityMessageId(u32); + +#[event_handler] +async fn handle_login_start( + event: PlayerStartLoginEvent, + state: GlobalState, +) -> NetResult { + if get_global_config().velocity.enabled { + let id = rand::random::(); + let mut writer = event.entity + .get_mut::(&state.clone())?; + writer.send_packet(&LoginPluginMessagePacket::<()>::new(id, String::from("velocity:player_info"), ()), &NetEncodeOpts::WithLength).await?; + state.universe.add_component(event.entity, VelocityMessageId(id))?; + + // this stops the packet handler from doing login success + Err(NetError::EventsError(EventsError::Cancelled)) + } else { + Ok(event) + } +} + +#[event_handler] +async fn handle_velocity_response( + event: LoginPluginResponseEvent, + state: GlobalState, +) -> NetResult { + let message = &event.packet; + if message.message_id.val as u32 == event.entity.get::(&state.clone())?.0 { + state.universe.remove_component::(event.entity)?; + + let len = message.data.len(); + + let mut signature = vec![0u8; 32]; + let mut data = Vec::with_capacity(256); + let mut buf = Cursor::new(&message.data); + + if len > 0 && message.success { + buf.read_exact(&mut signature).await?; + + let index = buf.position(); + buf.read_to_end(&mut data).await?; + buf.set_position(index); + + let version = VarInt::decode(&mut buf, &NetDecodeOpts::None)?; + let _addr = String::decode(&mut buf, &NetDecodeOpts::None)?; + + if version != 1 { + return Err(NetError::kick(TextComponentBuilder::new("[FerrumC]") + .color(NamedColor::Blue) + .space() + + ComponentBuilder::text("This velocity modern forwarding version is not supported!") + .color(NamedColor::Red) + .build())); + } + } else { + return Err(NetError::kick(ComponentBuilder::text("[FerrumC]") + .color(NamedColor::Blue) + .space() + + ComponentBuilder::text("The velocity proxy did not send forwarding information!") + .color(NamedColor::Red) + .build())); + } + + let mut key = HmacSha256::new_from_slice(get_global_config().velocity.secret.as_bytes()) + .expect("Failed to create HmacSha256 for velocity secret"); + key.update(&data); + + if key.verify_slice(&signature[..]).is_ok() { + crate::send_login_success( + state.clone(), + event.entity, + PlayerIdentity::decode(&mut buf, &NetDecodeOpts::None)?, + ).await?; + + Ok(event) + } else { + Err(NetError::kick("Invalid proxy response!".to_string())) + } + } else { + Ok(event) + } +} diff --git a/src/lib/core/Cargo.toml b/src/lib/core/Cargo.toml index 79e2e1c3..3d842d48 100644 --- a/src/lib/core/Cargo.toml +++ b/src/lib/core/Cargo.toml @@ -7,4 +7,10 @@ edition = "2021" [dependencies] thiserror = { workspace = true } tokio = { workspace = true} -ferrumc-ecs = {workspace = true} +ferrumc-ecs = { workspace = true } + +# this could cause issues +ferrumc-net-codec = { workspace = true } +ferrumc-macros = { workspace = true } +ferrumc-config = { workspace = true } +flate2 = { workspace = true } diff --git a/src/lib/core/src/identity/player_identity.rs b/src/lib/core/src/identity/player_identity.rs index a8b33e9d..4a056a84 100644 --- a/src/lib/core/src/identity/player_identity.rs +++ b/src/lib/core/src/identity/player_identity.rs @@ -1,11 +1,93 @@ -#[derive(Debug)] +use ferrumc_macros::{NetEncode, NetDecode}; +use ferrumc_net_codec::{ + decode::{NetDecode, NetDecodeResult, NetDecodeOpts}, + encode::{NetEncode, NetEncodeResult, NetEncodeOpts}, + net_types::length_prefixed_vec::LengthPrefixedVec +}; +use std::io::{Write, Read}; +use tokio::io::AsyncWrite; + +#[derive(Eq, PartialEq, Clone, Debug, NetEncode, NetDecode)] +/// The PlayerIdentity holds information about a player. +/// +/// Fields: +/// `uuid`: The uuid of the PlayerIdentity. +/// `username`: The username of the PlayerIdentity. +/// `properties`: The properties of the PlayerIdentity for example textures. +/// +/// ```ignore +/// PlayerIdentity { +/// uuid: Uuid::new_v4().as_u128(), +/// username: String::from("Name"), +/// properties: vec![IdentityProperty { +/// name: String::from("textures"), +/// value: String::from("ewogICJ0aW1lc3RhbXAiIDog..."), +/// is_signed: false, +/// signature: None, +/// }], +/// } +/// ``` +/// pub struct PlayerIdentity { - pub username: String, + /// The uuid of this Identity pub uuid: u128, + /// The username of this Identity + pub username: String, + /// The properties of this Identity + pub properties: LengthPrefixedVec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Signature(pub Option); + +impl NetEncode for Signature { + fn encode(&self, writer: &mut W, opts: &NetEncodeOpts) -> NetEncodeResult<()> { + (self.0.is_some()).encode(writer, opts)?; + (self.0).encode(writer, opts)?; + Ok(()) + } + + async fn encode_async(&self, writer: &mut W, opts: &NetEncodeOpts) -> NetEncodeResult<()> { + (self.0.is_some()).encode_async(writer, opts).await?; + (self.0).encode_async(writer, opts).await?; + Ok(()) + } +} + +impl NetDecode for Signature { + fn decode(reader: &mut R, opts: &NetDecodeOpts) -> NetDecodeResult { + if bool::decode(reader, opts)? { + Ok(Signature(Some(::decode(reader, opts)?))) + } else { + Ok(Signature(None)) + } + } +} + +#[derive(Eq, PartialEq, Clone, Debug, NetEncode, NetDecode)] +/// A property of a PlayerIdentity. +/// +/// Fields: +/// `name`: The name of the Property. +/// `value`: The value of the Property. +/// `signature`: The signature of the Property +/// +pub struct IdentityProperty { + /// The name of this Property. + pub name: String, + /// The value of this Property. + pub value: String, + /// The signature of this Property. + pub signature: Signature, } impl PlayerIdentity { + /// Create a new PlayerIdentity from uuid and username. pub fn new(username: String, uuid: u128) -> Self { - Self { username, uuid } + Self { + username, + uuid, + properties: LengthPrefixedVec::new(vec![]) + } } } diff --git a/src/lib/derive_macros/src/events/mod.rs b/src/lib/derive_macros/src/events/mod.rs index ac4e0f95..6db2f3f8 100644 --- a/src/lib/derive_macros/src/events/mod.rs +++ b/src/lib/derive_macros/src/events/mod.rs @@ -123,6 +123,7 @@ pub(crate) fn derive(input: TokenStream) -> TokenStream { quote! {crate} } FoundCrate::Name(name) => { + let name = format_ident!("{}", name); quote! {::#name} } }; diff --git a/src/lib/events/src/errors.rs b/src/lib/events/src/errors.rs index 7d48b763..ed646c5c 100644 --- a/src/lib/events/src/errors.rs +++ b/src/lib/events/src/errors.rs @@ -9,4 +9,8 @@ pub enum EventsError { }, #[error("A listener failed")] ListenerFailed, + #[error("cancelled")] + Cancelled, + #[error("{0}")] + Other(String), } diff --git a/src/lib/events/src/infrastructure.rs b/src/lib/events/src/infrastructure.rs index 25f612ce..c3ceeb50 100644 --- a/src/lib/events/src/infrastructure.rs +++ b/src/lib/events/src/infrastructure.rs @@ -72,11 +72,13 @@ pub trait Event: Sized + Send + Sync + 'static { /// will give its result to the next one with a lesser priority and so on. /// /// Returns `Ok(())` if the execution succeeded. `Err(EventsError)` ifa listener failed. - async fn trigger(event: Self::Data, state: Self::State) -> Result<(), Self::Error> { - let listeners = EVENTS_LISTENERS - .get(Self::name()) - .expect("Failed to find event listeners. Impossible;"); - + async fn trigger(event: Self::Data, state: Self::State) -> Result { + let listeners = match EVENTS_LISTENERS.get(Self::name()) { + Some(listeners) => listeners, + None => { + return Ok(event); + } + }; // Convert listeners iterator into Stream stream::iter(listeners.iter()) // TODO: Remove this since it's not possible to have a wrong type in the map of the event??? @@ -94,9 +96,7 @@ pub trait Event: Sized + Send + Sync + 'static { } } }) - .await?; - - Ok(()) + .await } /*/// Trigger the execution of an event with concurrency support diff --git a/src/lib/net/Cargo.toml b/src/lib/net/Cargo.toml index dd1ad654..cc72285d 100644 --- a/src/lib/net/Cargo.toml +++ b/src/lib/net/Cargo.toml @@ -31,3 +31,4 @@ uuid = { workspace = true, features = ["v4"] } async-trait = { workspace = true } byteorder = { workspace = true } ferrumc-state = { workspace = true } +bitmask-enum = { workspace = true } diff --git a/src/lib/net/crates/codec/src/decode/primitives.rs b/src/lib/net/crates/codec/src/decode/primitives.rs index 3e00e794..6e1b562b 100644 --- a/src/lib/net/crates/codec/src/decode/primitives.rs +++ b/src/lib/net/crates/codec/src/decode/primitives.rs @@ -39,6 +39,12 @@ impl_for_primitives!( f64 ); +impl NetDecode for () { + fn decode(_reader: &mut R, _: &NetDecodeOpts) -> NetDecodeResult { + Ok(()) + } +} + impl NetDecode for bool { fn decode(reader: &mut R, _: &NetDecodeOpts) -> NetDecodeResult { Ok(::decode(reader, &NetDecodeOpts::None)? != 0) diff --git a/src/lib/net/crates/codec/src/encode/primitives.rs b/src/lib/net/crates/codec/src/encode/primitives.rs index b90ffc79..e71fa7ea 100644 --- a/src/lib/net/crates/codec/src/encode/primitives.rs +++ b/src/lib/net/crates/codec/src/encode/primitives.rs @@ -44,6 +44,16 @@ impl_for_primitives!( f64 ); +impl NetEncode for () { + fn encode(&self, _writer: &mut W, _: &NetEncodeOpts) -> NetEncodeResult<()> { + Ok(()) + } + + async fn encode_async(&self, _writer: &mut W, _: &NetEncodeOpts) -> NetEncodeResult<()> { + Ok(()) + } +} + impl NetEncode for bool { fn encode(&self, writer: &mut W, _: &NetEncodeOpts) -> NetEncodeResult<()> { (*self as u8).encode(writer, &NetEncodeOpts::None) diff --git a/src/lib/net/crates/codec/src/net_types/length_prefixed_vec.rs b/src/lib/net/crates/codec/src/net_types/length_prefixed_vec.rs index 2f0edabd..d1bd0fb0 100644 --- a/src/lib/net/crates/codec/src/net_types/length_prefixed_vec.rs +++ b/src/lib/net/crates/codec/src/net_types/length_prefixed_vec.rs @@ -4,18 +4,26 @@ use crate::net_types::var_int::VarInt; use std::io::{Read, Write}; use tokio::io::AsyncWrite; -#[derive(Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct LengthPrefixedVec { - pub length: VarInt, pub data: Vec, } impl LengthPrefixedVec { pub fn new(data: Vec) -> Self { - Self { - length: VarInt::new(data.len() as i32), - data, - } + Self { data } + } + + pub fn push(&mut self, value: T) { + self.data.push(value); + } + + pub fn pop(&mut self) -> Option { + self.data.pop() + } + + pub fn length(&self) -> usize { + self.data.len() } } @@ -24,7 +32,7 @@ where T: NetEncode, { fn encode(&self, writer: &mut W, opts: &NetEncodeOpts) -> NetEncodeResult<()> { - self.length.encode(writer, opts)?; + VarInt::from(self.length()).encode(writer, opts)?; for item in &self.data { item.encode(writer, opts)?; @@ -38,7 +46,7 @@ where writer: &mut W, opts: &NetEncodeOpts, ) -> NetEncodeResult<()> { - self.length.encode_async(writer, opts).await?; + VarInt::from(self.length()).encode_async(writer, opts).await?; for item in &self.data { item.encode_async(writer, opts).await?; @@ -47,6 +55,7 @@ where Ok(()) } } + impl NetDecode for LengthPrefixedVec where T: NetDecode, @@ -59,6 +68,6 @@ where data.push(T::decode(reader, opts)?); } - Ok(Self { length, data }) + Ok(Self { data }) } } diff --git a/src/lib/net/crates/codec/src/net_types/var_int.rs b/src/lib/net/crates/codec/src/net_types/var_int.rs index 33231791..42010c81 100644 --- a/src/lib/net/crates/codec/src/net_types/var_int.rs +++ b/src/lib/net/crates/codec/src/net_types/var_int.rs @@ -8,7 +8,7 @@ use deepsize::DeepSizeOf; use std::io::{Read, Write}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -#[derive(Debug, Encode, Decode, Clone, DeepSizeOf)] +#[derive(Debug, Encode, Decode, Clone, DeepSizeOf, Eq)] pub struct VarInt { /// The value of the VarInt. pub val: i32, diff --git a/src/lib/net/src/connection.rs b/src/lib/net/src/connection.rs index 63f190b4..01a7a379 100644 --- a/src/lib/net/src/connection.rs +++ b/src/lib/net/src/connection.rs @@ -1,6 +1,7 @@ use crate::packets::incoming::packet_skeleton::PacketSkeleton; use crate::utils::state::terminate_connection; -use crate::{handle_packet, NetResult}; +use crate::{handle_packet, NetResult, packets::outgoing::disconnect::DISCONNECT_STRING}; +use crate::errors::NetError; use ferrumc_net_codec::encode::NetEncode; use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_state::ServerState; @@ -29,6 +30,7 @@ impl Default for ConnectionControl { Self::new() } } + #[derive(Clone)] pub enum ConnectionState { Handshaking, @@ -37,6 +39,7 @@ pub enum ConnectionState { Play, Configuration, } + impl ConnectionState { pub fn as_str(&self) -> &'static str { match self { @@ -159,15 +162,22 @@ pub async fn handle_connection(state: Arc, tcp_stream: TcpStream) - .instrument(debug_span!("eid", %entity)) .inner() { - warn!( - "Failed to handle packet: {:?}. packet_id: {:02X}; conn_state: {}", - e, - packet_skele.id, - conn_state.as_str() - ); - // Kick the player (when implemented). - terminate_connection(state.clone(), entity, "Failed to handle packet".to_string()) - .await?; + match e { + NetError::Kick(msg) => { + terminate_connection(state.clone(), entity, msg.clone()) + .await?; + }, + _ => { + warn!( + "Failed to handle packet: {:?}. packet_id: {:02X}; conn_state: {}", + e, + packet_skele.id, + conn_state.as_str() + ); + terminate_connection(state.clone(), entity, DISCONNECT_STRING.to_string()) + .await?; + } + } break 'recv; }; } diff --git a/src/lib/net/src/errors.rs b/src/lib/net/src/errors.rs index d24d3684..bf412191 100644 --- a/src/lib/net/src/errors.rs +++ b/src/lib/net/src/errors.rs @@ -43,6 +43,15 @@ pub enum NetError { #[error("{0}")] Chunk(#[from] ChunkError), + + #[error("{0}")] + Kick(ferrumc_text::TextComponent), +} + +impl NetError { + pub fn kick(reason: impl Into) -> Self { + Self::Kick(reason.into()) + } } #[derive(Debug, Error)] diff --git a/src/lib/net/src/packets/incoming/server_bound_plugin_message.rs b/src/lib/net/src/packets/incoming/server_bound_plugin_message.rs index a23a532f..eac83571 100644 --- a/src/lib/net/src/packets/incoming/server_bound_plugin_message.rs +++ b/src/lib/net/src/packets/incoming/server_bound_plugin_message.rs @@ -1,21 +1,42 @@ +use std::io::Read; +use std::sync::Arc; +use tracing::trace; +use ferrumc_macros::{packet, Event}; +use ferrumc_net_codec::decode::{NetDecode, NetDecodeOpts, NetDecodeResult}; +use ferrumc_net_codec::net_types::var_int::VarInt; use crate::packets::IncomingPacket; use crate::NetResult; -use ferrumc_macros::packet; -use ferrumc_net_codec::decode::{NetDecode, NetDecodeOpts, NetDecodeResult}; use ferrumc_state::ServerState; -use std::io::Read; -use std::sync::Arc; -use tracing::debug; +use std::fmt::Debug; +use ferrumc_events::infrastructure::Event; + +/// This event triggers when a [LoginPluginResponse] is received. +/// +#[derive(Event, Debug)] +pub struct LoginPluginResponseEvent { + /// The entity that the event was triggered for + pub entity: usize, + /// The [LoginPluginResponse] packet received. + pub packet: LoginPluginResponse, +} #[derive(Debug)] #[packet(packet_id = 0x02, state = "configuration")] pub struct ServerBoundPluginMessage { - channel: String, - data: Vec, + pub channel: String, + pub data: Vec, +} + +#[derive(Debug, Clone)] +#[packet(packet_id = 0x02, state = "login")] +pub struct LoginPluginResponse { + pub message_id: VarInt, + pub success: bool, + pub data: Vec, } pub struct ClientMinecraftBrand { - pub brand: String, + pub brand: String } impl NetDecode for ServerBoundPluginMessage { @@ -24,23 +45,53 @@ impl NetDecode for ServerBoundPluginMessage { let mut buf = Vec::::new(); reader.read_to_end(&mut buf)?; - Ok(Self { channel, data: buf }) + Ok(Self { + channel, + data: buf + }) } } impl IncomingPacket for ServerBoundPluginMessage { async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { - debug!("Received plugin message: {:?}", self); + trace!("Received plugin message: {:?}", self); if self.channel == "minecraft:brand" { let brand = String::from_utf8(self.data)?; - debug!("Client brand: {}", brand); + trace!("Client brand: {}", brand); + + state.universe.add_component(conn_id, ClientMinecraftBrand { brand })?; + } - state - .universe - .add_component(conn_id, ClientMinecraftBrand { brand })?; + Ok(()) + } +} + +impl NetDecode for LoginPluginResponse { + fn decode(reader: &mut R, opts: &NetDecodeOpts) -> NetDecodeResult { + let message_id = ::decode(reader, opts)?; + let success = ::decode(reader, opts)?; + + let mut buf = Vec::::new(); + if success { + reader.read_to_end(&mut buf)?; } + Ok(Self { + message_id, + success, + data: buf + }) + } +} + +impl IncomingPacket for LoginPluginResponse { + async fn handle(self, conn_id: usize, state: Arc) -> NetResult<()> { + LoginPluginResponseEvent::trigger(LoginPluginResponseEvent { + entity: conn_id, + packet: self, + }, Arc::clone(&state)).await?; + Ok(()) } } diff --git a/src/lib/net/src/packets/outgoing/client_bound_plugin_message.rs b/src/lib/net/src/packets/outgoing/client_bound_plugin_message.rs new file mode 100644 index 00000000..843d6019 --- /dev/null +++ b/src/lib/net/src/packets/outgoing/client_bound_plugin_message.rs @@ -0,0 +1,74 @@ +use ferrumc_macros::{packet, NetEncode}; +use ferrumc_net_codec::{ + encode::NetEncode, + net_types::var_int::VarInt +}; +use std::io::Write; + +#[derive(NetEncode)] +#[packet(packet_id = 0x01)] +pub struct ConfigurationPluginMessagePacket +where + T: NetEncode { + pub channel: String, + pub data: T, +} + +#[derive(NetEncode)] +#[packet(packet_id = 0x19)] +pub struct PlayPluginMessagePacket +where + T: NetEncode { + pub channel: String, + pub data: T, +} + +#[derive(NetEncode, Clone)] +#[packet(packet_id = 0x04)] +pub struct LoginPluginMessagePacket +where + T: NetEncode, +{ + pub message_id: VarInt, + pub channel: String, + pub data: T, +} + +impl ConfigurationPluginMessagePacket +where + T: NetEncode { + pub fn new(channel: String, data: T) -> Self + { + Self { + channel, + data + } + } +} + +impl PlayPluginMessagePacket +where + T: NetEncode, +{ + pub fn new(channel: String, data: T) -> Self + { + Self { + channel, + data + } + } +} + +impl LoginPluginMessagePacket +where + T: NetEncode, +{ + pub fn new(id: u32, channel: String, data: T) -> Self + { + Self { + message_id: VarInt::new(id as i32), + channel, + data + } + } +} diff --git a/src/lib/net/src/packets/outgoing/disconnect.rs b/src/lib/net/src/packets/outgoing/disconnect.rs index 2b7dfb0a..8d0ee9a3 100644 --- a/src/lib/net/src/packets/outgoing/disconnect.rs +++ b/src/lib/net/src/packets/outgoing/disconnect.rs @@ -1,27 +1,57 @@ +use crate::connection::ConnectionState; +use crate::{errors::NetError, NetResult}; use ferrumc_macros::{packet, NetEncode}; -use ferrumc_text::{ComponentBuilder, TextComponent}; +use ferrumc_text::*; use std::io::Write; +pub const DISCONNECT_STRING: &str = "&cDisconnected"; + +#[derive(NetEncode)] +pub enum DisconnectPacket { + Login(LoginDisconnectPacket), + Play(PlayDisconnectPacket), +} + +#[derive(NetEncode)] +#[packet(packet_id = 0x00)] +pub struct LoginDisconnectPacket { + pub reason: JsonTextComponent, +} + #[derive(NetEncode)] #[packet(packet_id = 0x1D)] -pub struct DisconnectPacket { +pub struct PlayDisconnectPacket { pub reason: TextComponent, } impl DisconnectPacket { - pub fn new(reason: TextComponent) -> Self { - Self { reason } + pub fn from>(state: &ConnectionState, reason: C) -> NetResult { + match state { + ConnectionState::Login => { + Ok(DisconnectPacket::Login(LoginDisconnectPacket::new(reason.into()))) + } + ConnectionState::Play => { + Ok(DisconnectPacket::Play(PlayDisconnectPacket::new(reason))) + } + _ => { + Err(NetError::InvalidState(state.clone() as u8)) + } + } } - pub fn from_string(reason: String) -> Self { - let reason = ComponentBuilder::text(reason); +} + +impl LoginDisconnectPacket { + pub fn new>(reason: C) -> Self { Self { - reason: reason.build(), + reason: reason.into(), } } } -impl Default for DisconnectPacket { - fn default() -> Self { - Self::from_string("FERRUMC-DISCONNECTED".to_string()) +impl PlayDisconnectPacket { + pub fn new>(reason: C) -> Self { + Self { + reason: reason.into(), + } } } diff --git a/src/lib/net/src/packets/outgoing/login_success.rs b/src/lib/net/src/packets/outgoing/login_success.rs index c244ee30..6fda7bb6 100644 --- a/src/lib/net/src/packets/outgoing/login_success.rs +++ b/src/lib/net/src/packets/outgoing/login_success.rs @@ -1,22 +1,18 @@ +use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_macros::{packet, NetEncode}; -use ferrumc_net_codec::net_types::var_int::VarInt; use std::io::Write; #[derive(NetEncode)] #[packet(packet_id = 0x02)] -pub struct LoginSuccessPacket<'a> { - pub uuid: u128, - pub username: &'a str, - pub number_of_properties: VarInt, +pub struct LoginSuccessPacket { + pub identity: PlayerIdentity, pub strict_error_handling: bool, } -impl<'a> LoginSuccessPacket<'a> { - pub fn new(uuid: u128, username: &'a str) -> Self { +impl LoginSuccessPacket { + pub fn new(identity: PlayerIdentity) -> Self { Self { - uuid, - username, - number_of_properties: VarInt::from(0), + identity, strict_error_handling: false, } } diff --git a/src/lib/net/src/packets/outgoing/mod.rs b/src/lib/net/src/packets/outgoing/mod.rs index 9c63db7c..8a334cf2 100644 --- a/src/lib/net/src/packets/outgoing/mod.rs +++ b/src/lib/net/src/packets/outgoing/mod.rs @@ -14,3 +14,5 @@ pub mod set_render_distance; pub mod status_response; pub mod synchronize_player_position; pub mod update_time; +pub mod player_info_update; +pub mod client_bound_plugin_message; diff --git a/src/lib/net/src/packets/outgoing/player_info_update.rs b/src/lib/net/src/packets/outgoing/player_info_update.rs new file mode 100644 index 00000000..e5f1fccd --- /dev/null +++ b/src/lib/net/src/packets/outgoing/player_info_update.rs @@ -0,0 +1,95 @@ +use ferrumc_macros::{NetEncode, packet}; +use ferrumc_net_codec::net_types::{ + var_int::VarInt, + length_prefixed_vec::LengthPrefixedVec, +}; +use ferrumc_core::identity::player_identity::*; +use bitmask_enum::bitmask; +use std::io::Write; + +#[bitmask(u8)] +#[derive(NetEncode)] +pub enum PlayerActions { + AddPlayer = 0x01, + InitializeChat = 0x02, + UpdateGameMode = 0x04, + UpdateListed = 0x08, + UpdateLatency = 0x10, + UpdateDisplayName = 0x20, +} + +#[derive(NetEncode, Debug, Eq, PartialEq, Clone)] +pub enum PlayerAction { + AddPlayer { + username: String, + properties: LengthPrefixedVec, + }, + InitializeChat { // TODO + }, + UpdateGameMode(VarInt), + UpdateListed(bool), + UpdateLatency(VarInt), + UpdateDisplayName { // TODO + }, +} + +impl PlayerAction { + pub fn flags(&self) -> PlayerActions { + match self { + Self::AddPlayer { .. } => PlayerActions::AddPlayer, + Self::InitializeChat { .. } => PlayerActions::InitializeChat, + Self::UpdateGameMode(..) => PlayerActions::UpdateGameMode, + Self::UpdateListed(..) => PlayerActions::UpdateListed, + Self::UpdateLatency(..) => PlayerActions::UpdateLatency, + Self::UpdateDisplayName { .. } => PlayerActions::UpdateDisplayName, + } + } +} + +#[derive(NetEncode, Debug, Eq, PartialEq)] +pub struct PlayerInfo { + pub uuid: u128, + pub actions: Vec, +} + +impl PlayerInfo { + pub fn from(profile: &PlayerIdentity) -> Self { + Self { + uuid: profile.uuid, + actions: vec![ + PlayerAction::AddPlayer { + username: profile.username.clone(), + properties: profile.properties.clone(), + }, + PlayerAction::UpdateListed(true), + ], + } + } +} + +#[derive(NetEncode, Debug)] +#[packet(packet_id = 0x3E)] +pub struct PlayerInfoUpdatePacket { + actions: PlayerActions, + infos: LengthPrefixedVec, +} + +impl PlayerInfoUpdatePacket { + pub fn new(infos: Vec) -> Self { + Self { + actions: Self::get_actions(&infos), + infos: LengthPrefixedVec::new(infos), + } + } + + fn get_actions(infos: &[PlayerInfo]) -> PlayerActions { + let first = &infos[0].actions; + let mut flags = PlayerActions::none(); + + for action in first.iter() { + flags |= action.flags(); + } + + flags + } +} diff --git a/src/lib/net/src/utils/state.rs b/src/lib/net/src/utils/state.rs index 9431c361..53761a59 100644 --- a/src/lib/net/src/utils/state.rs +++ b/src/lib/net/src/utils/state.rs @@ -1,7 +1,7 @@ use crate::{ - connection::{ConnectionControl, StreamWriter}, + connection::{ConnectionControl, ConnectionState, StreamWriter}, errors::NetError, - packets::outgoing::disconnect::DisconnectPacket, + packets::outgoing::disconnect::*, NetResult, }; use ferrumc_net_codec::encode::NetEncodeOpts; @@ -25,7 +25,7 @@ use super::ecs_helpers::EntityExt; pub async fn terminate_connection( state: GlobalState, conn_id: usize, - reason: String, + reason: impl Into, ) -> NetResult<()> { let mut writer = match conn_id.get_mut::(&state.clone()) { Ok(writer) => writer, @@ -35,9 +35,11 @@ pub async fn terminate_connection( } }; + let conn_state = conn_id.get::(&state.clone())?; + if let Err(e) = writer .send_packet( - &DisconnectPacket::from_string(reason), + &DisconnectPacket::from(&conn_state, reason)?, &NetEncodeOpts::WithLength, ) .await diff --git a/src/lib/utils/config/src/server_config.rs b/src/lib/utils/config/src/server_config.rs index dc6a9907..91c12297 100644 --- a/src/lib/utils/config/src/server_config.rs +++ b/src/lib/utils/config/src/server_config.rs @@ -15,6 +15,7 @@ use serde_derive::{Deserialize, Serialize}; /// - `database` - [DatabaseConfig]: The configuration for the database. /// - `world`: The name of the world that the server will load. /// - `network_compression_threshold`: The threshold at which the server will compress network packets. +/// - `velocity`: Velocity settings. #[derive(Debug, Deserialize, Serialize)] pub struct ServerConfig { pub host: String, @@ -25,6 +26,20 @@ pub struct ServerConfig { pub database: DatabaseConfig, pub world: String, pub network_compression_threshold: i32, // Can be negative + #[serde(default)] + pub velocity: VelocityConfig, +} + +/// The velocity configuration section from [ServerConfig]. +/// +/// Fields: +/// - `enabled`: If velocity support should be enabled. +/// - `secret`: The velocity secret used for modern forwarding. +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct VelocityConfig { + /// see [velocity_secret](VelocityConfig::secret) + pub enabled: bool, + pub secret: String, } /// The database configuration section from [ServerConfig]. From cfeec4daaa74d8b81636216251b7d5bcb349ba3b Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:59:57 +0000 Subject: [PATCH 02/15] fix PlayerIdentity documentation --- src/lib/core/src/identity/player_identity.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/core/src/identity/player_identity.rs b/src/lib/core/src/identity/player_identity.rs index 4a056a84..0412f1a3 100644 --- a/src/lib/core/src/identity/player_identity.rs +++ b/src/lib/core/src/identity/player_identity.rs @@ -22,7 +22,6 @@ use tokio::io::AsyncWrite; /// properties: vec![IdentityProperty { /// name: String::from("textures"), /// value: String::from("ewogICJ0aW1lc3RhbXAiIDog..."), -/// is_signed: false, /// signature: None, /// }], /// } From f94bbe8001ede85aa8a6b33566e7b8448cca7637 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:49:13 +0000 Subject: [PATCH 03/15] Implement Suggestions --- src/bin/src/systems/keep_alive_system.rs | 5 +- .../src/net_types/length_prefixed_vec.rs | 21 ++-- src/lib/net/src/connection.rs | 6 +- .../net/src/packets/incoming/keep_alive.rs | 4 +- src/lib/net/src/utils/state.rs | 109 ++++++++++-------- 5 files changed, 79 insertions(+), 66 deletions(-) diff --git a/src/bin/src/systems/keep_alive_system.rs b/src/bin/src/systems/keep_alive_system.rs index 7fbb6df7..08d53259 100644 --- a/src/bin/src/systems/keep_alive_system.rs +++ b/src/bin/src/systems/keep_alive_system.rs @@ -5,7 +5,7 @@ use ferrumc_net::connection::{ConnectionState, StreamWriter}; use ferrumc_net::packets::incoming::keep_alive::IncomingKeepAlivePacket; use ferrumc_net::packets::outgoing::keep_alive::OutgoingKeepAlivePacket; use ferrumc_net::utils::broadcast::{BroadcastOptions, BroadcastToAll}; -use ferrumc_net::utils::state::terminate_connection; +use ferrumc_net::utils::state::TerminateConnectionPlayerExt; use ferrumc_state::GlobalState; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -76,9 +76,8 @@ impl System for KeepAliveSystem { if (current_time - keep_alive.timestamp) >= 30000 { // two iterations missed - if let Err(e) = terminate_connection( + if let Err(e) = entity.terminate_connection( state.clone(), - *entity, "Keep alive timeout".to_string(), ) .await diff --git a/src/lib/net/crates/codec/src/net_types/length_prefixed_vec.rs b/src/lib/net/crates/codec/src/net_types/length_prefixed_vec.rs index d1bd0fb0..d7b02c2f 100644 --- a/src/lib/net/crates/codec/src/net_types/length_prefixed_vec.rs +++ b/src/lib/net/crates/codec/src/net_types/length_prefixed_vec.rs @@ -1,6 +1,7 @@ use crate::decode::{NetDecode, NetDecodeOpts, NetDecodeResult}; use crate::encode::{NetEncode, NetEncodeOpts, NetEncodeResult}; use crate::net_types::var_int::VarInt; +use std::ops::{Deref, DerefMut}; use std::io::{Read, Write}; use tokio::io::AsyncWrite; @@ -13,17 +14,19 @@ impl LengthPrefixedVec { pub fn new(data: Vec) -> Self { Self { data } } +} - pub fn push(&mut self, value: T) { - self.data.push(value); - } +impl Deref for LengthPrefixedVec { + type Target = Vec; - pub fn pop(&mut self) -> Option { - self.data.pop() + fn deref(&self) -> &Self::Target { + &self.data } +} - pub fn length(&self) -> usize { - self.data.len() +impl DerefMut for LengthPrefixedVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data } } @@ -32,7 +35,7 @@ where T: NetEncode, { fn encode(&self, writer: &mut W, opts: &NetEncodeOpts) -> NetEncodeResult<()> { - VarInt::from(self.length()).encode(writer, opts)?; + VarInt::from(self.len()).encode(writer, opts)?; for item in &self.data { item.encode(writer, opts)?; @@ -46,7 +49,7 @@ where writer: &mut W, opts: &NetEncodeOpts, ) -> NetEncodeResult<()> { - VarInt::from(self.length()).encode_async(writer, opts).await?; + VarInt::from(self.len()).encode_async(writer, opts).await?; for item in &self.data { item.encode_async(writer, opts).await?; diff --git a/src/lib/net/src/connection.rs b/src/lib/net/src/connection.rs index 01a7a379..73cc15a5 100644 --- a/src/lib/net/src/connection.rs +++ b/src/lib/net/src/connection.rs @@ -1,5 +1,5 @@ use crate::packets::incoming::packet_skeleton::PacketSkeleton; -use crate::utils::state::terminate_connection; +use crate::utils::state::TerminateConnectionPlayerExt; use crate::{handle_packet, NetResult, packets::outgoing::disconnect::DISCONNECT_STRING}; use crate::errors::NetError; use ferrumc_net_codec::encode::NetEncode; @@ -164,7 +164,7 @@ pub async fn handle_connection(state: Arc, tcp_stream: TcpStream) - { match e { NetError::Kick(msg) => { - terminate_connection(state.clone(), entity, msg.clone()) + entity.terminate_connection(state.clone(), msg.clone()) .await?; }, _ => { @@ -174,7 +174,7 @@ pub async fn handle_connection(state: Arc, tcp_stream: TcpStream) - packet_skele.id, conn_state.as_str() ); - terminate_connection(state.clone(), entity, DISCONNECT_STRING.to_string()) + entity.terminate_connection(state.clone(), DISCONNECT_STRING.to_string()) .await?; } } diff --git a/src/lib/net/src/packets/incoming/keep_alive.rs b/src/lib/net/src/packets/incoming/keep_alive.rs index a71239f0..e834702f 100644 --- a/src/lib/net/src/packets/incoming/keep_alive.rs +++ b/src/lib/net/src/packets/incoming/keep_alive.rs @@ -1,6 +1,6 @@ use crate::packets::outgoing::keep_alive::OutgoingKeepAlivePacket; use crate::packets::IncomingPacket; -use crate::utils::state::terminate_connection; +use crate::utils::state::TerminateConnectionPlayerExt; use crate::NetResult; use ferrumc_macros::{packet, NetDecode}; use ferrumc_state::ServerState; @@ -22,7 +22,7 @@ impl IncomingPacket for IncomingKeepAlivePacket { conn_id, self.timestamp, last_sent_keep_alive.timestamp ); if let Err(e) = - terminate_connection(state, conn_id, "Invalid keep alive packet".to_string()).await + conn_id.terminate_connection(state, "Invalid keep alive packet".to_string()).await { debug!("Error terminating connection: {:?}", e); } diff --git a/src/lib/net/src/utils/state.rs b/src/lib/net/src/utils/state.rs index 53761a59..2bcb5c19 100644 --- a/src/lib/net/src/utils/state.rs +++ b/src/lib/net/src/utils/state.rs @@ -10,61 +10,72 @@ use tracing::{trace, warn}; use super::ecs_helpers::EntityExt; -// used codium for this function comment, very useful +pub trait TerminateConnectionPlayerExt { + #[allow(async_fn_in_trait)] + async fn terminate_connection( + &self, + state: GlobalState, + reason: impl Into, + ) -> NetResult<()>; +} -/// Terminates the connection of an entity with the given `conn_id`. -/// -/// Sends a disconnect packet with the given `reason` to the client, and marks the connection as -/// terminated. This will cause the connection to be dropped on the next tick of the -/// `ConnectionSystem`. -/// -/// # Errors -/// -/// Returns an error if the stream writer or connection control component cannot be accessed for -/// the given `conn_id`. -pub async fn terminate_connection( - state: GlobalState, - conn_id: usize, - reason: impl Into, -) -> NetResult<()> { - let mut writer = match conn_id.get_mut::(&state.clone()) { - Ok(writer) => writer, - Err(e) => { - warn!("Failed to get stream writer for entity {}: {}", conn_id, e); - return Err(NetError::ECSError(e)); - } - }; +impl TerminateConnectionPlayerExt for usize { + // used codium for this function comment, very useful - let conn_state = conn_id.get::(&state.clone())?; + /// Terminates the connection of an entity with the given `conn_id`. + /// + /// Sends a disconnect packet with the given `reason` to the client, and marks the connection as + /// terminated. This will cause the connection to be dropped on the next tick of the + /// `ConnectionSystem`. + /// + /// # Errors + /// + /// Returns an error if the stream writer or connection control component cannot be accessed for + /// the given `conn_id`. + async fn terminate_connection( + &self, + state: GlobalState, + reason: impl Into, + ) -> NetResult<()> { + let mut writer = match self.get_mut::(&state.clone()) { + Ok(writer) => writer, + Err(e) => { + warn!("Failed to get stream writer for entity {}: {}", self, e); + return Err(NetError::ECSError(e)); + } + }; - if let Err(e) = writer - .send_packet( - &DisconnectPacket::from(&conn_state, reason)?, - &NetEncodeOpts::WithLength, - ) - .await - { - warn!( - "Failed to send disconnect packet to entity {}: {}", - conn_id, e - ); - return Err(e); - } + let conn_state = self.get::(&state.clone())?; - match conn_id.get_mut::(&state.clone()) { - Ok(mut control) => { - control.should_disconnect = true; - - trace!("Set should_disconnect to true for entity {}", conn_id); - } - Err(e) => { + if let Err(e) = writer + .send_packet( + &DisconnectPacket::from(&conn_state, reason)?, + &NetEncodeOpts::WithLength, + ) + .await + { warn!( - "Failed to get connection control for entity {}: {}", - conn_id, e + "Failed to send disconnect packet to entity {}: {}", + self, e ); - return Err(NetError::ECSError(e)); + return Err(e); } - } - Ok(()) + match self.get_mut::(&state.clone()) { + Ok(mut control) => { + control.should_disconnect = true; + + trace!("Set should_disconnect to true for entity {}", self); + } + Err(e) => { + warn!( + "Failed to get connection control for entity {}: {}", + self, e + ); + return Err(NetError::ECSError(e)); + } + } + + Ok(()) + } } From 13bf0b5228aa6a07652b09bf643b290ae9ea1c7e Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:25:25 +0000 Subject: [PATCH 04/15] test --- src/lib/text/src/message/ast.rs | 21 + src/lib/text/src/message/errors.rs | 96 +++++ src/lib/text/src/message/mod.rs | 381 ++++++++++++++++++ .../text/src/message/tags/color_resolver.rs | 63 +++ src/lib/text/src/message/tags/mod.rs | 87 ++++ .../text/src/message/tags/reset_resolver.rs | 6 + src/lib/text/src/message/tests.rs | 25 ++ 7 files changed, 679 insertions(+) create mode 100644 src/lib/text/src/message/ast.rs create mode 100644 src/lib/text/src/message/errors.rs create mode 100644 src/lib/text/src/message/mod.rs create mode 100644 src/lib/text/src/message/tags/color_resolver.rs create mode 100644 src/lib/text/src/message/tags/mod.rs create mode 100644 src/lib/text/src/message/tags/reset_resolver.rs create mode 100644 src/lib/text/src/message/tests.rs diff --git a/src/lib/text/src/message/ast.rs b/src/lib/text/src/message/ast.rs new file mode 100644 index 00000000..9f8d7a70 --- /dev/null +++ b/src/lib/text/src/message/ast.rs @@ -0,0 +1,21 @@ +// - empty text component +// test - literal +// - tag +// test - literal +// +// hello - literal +// +pub struct RootTag<'a> { + pub children: Vec>, +} + +pub struct ParsedTag<'a> { + pub name: &'a str, + pub has_end_tag: bool, + pub children: Vec>, +} + +pub enum Tag<'a> { + Literal(&'a str), + Tag(ParsedTag<'a>), +} diff --git a/src/lib/text/src/message/errors.rs b/src/lib/text/src/message/errors.rs new file mode 100644 index 00000000..27a15c43 --- /dev/null +++ b/src/lib/text/src/message/errors.rs @@ -0,0 +1,96 @@ +use crate::message::Span; +use colored::Colorize; +use nom::error::{ErrorKind, FromExternalError, ParseError}; +use std::fmt; +use thiserror::Error; + +#[derive(Debug, PartialEq, Clone)] +pub struct MessageError<'a> { + /*pub input: &'a str, + pub line: usize, + pub column_start: usize, + pu*b column_width: usize,*/ + pub span: Span<'a>, + pub source: InnerMessageError, +} + +impl<'a> ParseError> for MessageError<'a> { + fn from_error_kind(input: Span<'a>, kind: ErrorKind) -> Self { + Self { + span: input, + source: InnerMessageError::NomError(kind), + } + } + + fn append(input: Span<'a>, kind: ErrorKind, _other: Self) -> Self { + Self::from_error_kind(input, kind) + } +} + +impl<'a, E> FromExternalError, E> for MessageError<'a> { + fn from_external_error(input: Span<'a>, kind: ErrorKind, _e: E) -> Self { + Self::from_error_kind(input, kind) + } +} + +#[derive(Debug, PartialEq, Clone, Error)] +pub enum InnerMessageError { + #[error("{0:#?}")] + NomError(ErrorKind), + #[error("Unexpected end tag (expected \"\", found \"\")")] + ExpectedEndTag(&'static str, &'static str), + #[error("Missing end tag for \"<{0}>\"")] + MissingEndTag(&'static str), + #[error("Unexpected end tag for \"<{0}>\"")] + UnexpectedEndTag(&'static str), + #[error("Missing start tag for \"\"")] + MissingStartTag(&'static str), +} + +#[derive(Debug, PartialEq, Clone, Error)] +pub enum ResolveError { + #[error("Unable to parse a {expected_type} from '{expected}'. {description}")] + ExpectedType { + expected_type: &'static str, + expected: String, + description: String, + }, + #[error("Process function is unimplemented for tag resolver \"<{0}>\"")] + ProcessUnimplemented(String), + #[error("Couldn't find resolver for tag \"<{0}>\"")] + NoResolverFor(String), + #[error("{0}")] + Custom(String), + #[error("{0}")] + CustomStatic(&'static str), +} + +fn fill_carets(col: (usize, usize), caret: &str) -> String { + format!("{}{}", " ".repeat(col.0), caret.repeat(col.1)) +} + +impl fmt::Display for MessageError<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let line = self.span.location_line().to_string(); + let space = " ".repeat(std::cmp::max(5 - (2 + line.len()), 0)); + let ln = " |".blue().to_string(); + write!( + f, + "{}: + \r {ln}{space} + \r {ln}{space}{} + \r{num}{} + \r {ln}{space} + ", + "error".bright_red(), + self.span.fragment(), + format!( + "{} {}", + fill_carets((self.span.get_column(), self.span.location_offset()), "^"), + self.source + ) + .bright_red(), + num = line.blue().to_string() + &ln + &space + ) + } +} diff --git a/src/lib/text/src/message/mod.rs b/src/lib/text/src/message/mod.rs new file mode 100644 index 00000000..076803de --- /dev/null +++ b/src/lib/text/src/message/mod.rs @@ -0,0 +1,381 @@ +use nom_locate::LocatedSpan; + +pub(crate) use crate::*; +pub mod ast; +pub mod tags; + +mod errors; + +#[cfg(test)] +mod tests; + +pub use errors::*; + +use ast::*; +use tags::*; + +use nom::{ + bytes::complete::{tag, is_not}, + character::complete::char, + sequence::{tuple, delimited}, + multi::{many0, fold_many0}, + Err, Needed, +}; + +pub type Span<'a> = LocatedSpan<&'a str>; +pub type IResult<'a, O> = nom::IResult, O, MessageError<'a>>; + +#[derive(Default)] +pub struct ParserOptions { + pub strict: bool, +} + +pub enum Context<'a> { + Pre { + tag: ParsedTag<'a>, + clear_stack: bool, + }, + Parse { + builder: &'a mut TextComponentBuilder, + tag: &'a ParsedTag<'a>, + }, +} + +pub struct Parser { + resolvers: Vec>, + opts: ParserOptions, +} + +impl Parser { + pub fn new(opts: ParserOptions) -> Self { + Self { + resolvers: vec![Box::new(color_resolver::ColorResolver)], + opts, + } + } + + pub fn resolve<'a>(&self, context: &mut Context<'a>) -> Result<(), ResolveError> { + for resolver in &self.resolvers { + if resolver.call(context, &self.opts)? { + return Ok(()); + } + } + + match context { + Context::Parse { tag, .. } => Err(ResolveError::NoResolverFor(tag.name.to_string())), + Context::Pre { tag, .. } => Err(ResolveError::NoResolverFor(tag.name.to_string())), + } + } + + fn parse_ast_tag(&self, tag: &Tag) -> Result { + match tag { + Tag::Literal(literal) => Ok(TextComponentBuilder::new(*literal)), + Tag::Tag(tag) => { + let mut builder = TextComponentBuilder::new(""); + self.resolve(&mut Context::Parse { + builder: &mut builder, + tag, + })?; + + for child in &tag.children { + builder.extra_mut(self.parse_ast_tag(&child)?); + } + + Ok(builder) + } + } + } + + pub fn parse_ast(&self, ast: RootTag) -> Result { + let mut builder = TextComponentBuilder::new(""); + for child in ast.children { + builder.extra_mut(self.parse_ast_tag(&child)?); + } + + Ok(builder) + } + + pub fn parse_tag<'a>(&self, input: Span<'a>) -> IResult> { + if i.len() < 3 { + return Err(Err::Incomplete(Needed::new(3))); + } + + let (input, name) = delimited(char('<'), is_not(">"), char('>'))(input)?; + + let tag = ParsedTag { + name, + has_end_tag: false, + children: Vec::with_capacity(4), + }; + let context = Context::Pre { + tag, + clear_stack: false, + }; + + Ok((input, context)) + } + + pub fn deserialize<'a>(&self, input: Span<'a>) -> IResult> { + let mut root = RootTag { children: Vec::with_capacity(4) }; + //let mut stack: Vec> = Vec::with_capacity(16); + let (input, left): (Span<'a>, Vec>) = fold_many0( + move |input| { + let mut context = self.parse_tag(input)?; + self.resolve(&mut context)?; + context + }, + || Vec::with_capacity(16), + |mut acc: Vec<_>, item| { + if let Context::Pre { .., clear_stack } = item { + if clear_stack { + let prev_node = None; + while let Some(mut node) = acc.pop() { + if let Some(Context::Pre { tag, .. }) = prev_node.as_mut() { + prev.children.push(tag); + } + + prev_node = Some(node); + } + + if let Some(Context::Pre { tag, .. }) = prev_node { + root.children.push(prev_node); + } + } + } else { + acc.push(item); + } + acc + } + )(input)?; + + if left.len() > 0 { + left.clear(); + } + + Ok((input, root)) + } +} + +impl Default for Parser { + fn default() -> Self { + Self::new(ParserOptions::default()) + } +} + +/* +use crate::*; +use nom::{ + branch::alt, + bytes::complete::{is_not, tag, tag_no_case, take_until}, + character::complete::char, + combinator::{cut, fail, opt}, + error::{context, convert_error, ErrorKind, VerboseError, VerboseErrorKind}, + multi::{many0, fold_many0}, + sequence::{delimited, preceded, tuple}, + Finish, +}; + +type IResult = nom::IResult>; + +fn pretty_print_error(s: &str, mut e: VerboseError<&str>) -> String { + let (_root_s, root_error) = e.errors[0].clone(); + if matches!(root_error, VerboseErrorKind::Nom(ErrorKind::Fail)) + || matches!(root_error, VerboseErrorKind::Nom(ErrorKind::Eof)) + { + e.errors.remove(0); + } + convert_error(s, e) +} + +#[derive(Clone, Debug)] +pub struct ParsedTag<'a> { + pub name: &'a str, + pub text: Vec>, + pub arguments: Option<&'a str>, +} + +#[derive(Clone, Debug)] +pub enum ParsedMessage<'a> { + String(&'a str), + ParsedTag(ParsedTag<'a>), +} + +pub struct MiniMessageParser<'a> { + resolvers: HashMap< + &'a str, + Box Fn(&'b ParsedTag<'b>, TextComponentBuilder) -> TextComponentBuilder>, + >, + strict: bool, +} + +impl<'a> MiniMessageParser<'a> { + pub fn new(strict: bool) -> Self { + Self { + resolvers: HashMap::from([ + ( + "bold", + Box::new(|_: &ParsedTag<'_>, builder: TextComponentBuilder| builder.bold()) + as Box<_>, + ), + ( + "italic", + Box::new(|_: &ParsedTag<'_>, builder: TextComponentBuilder| builder.italic()) + as Box<_>, + ), + ( + "strikethrough", + Box::new(|_: &ParsedTag<'_>, builder: TextComponentBuilder| builder.strikethrough()) + as Box<_>, + ), + ]), + strict, + } + } + + pub fn parse_start_tag(&self, input: &'a str) -> IResult<&'a str, &'a str> { + delimited(tag("<"), is_not(">"), tag(">"))(input) + } + + pub fn parse_end_tag( + &self, + input: &'a str, + name: &'a str, + ) -> IResult<&'a str, Option<&'a str>> { + opt(delimited(tag("")))(input) + } + + pub fn parse_tag_values() -> impl FnMut(&'a str) -> IResult<&'a str, Option<&'a str>> { + preceded(char(':'), opt(take_until(">"))) + } + + pub fn parse_tag_args( + &self, + input: &'a str, + ) -> IResult<&'a str, (&'a str, Option<&'a str>)> { + tuple((take_until(":"), Self::parse_tag_values()))(input).or(Ok((input, (input, None)))) + } + + pub fn parse_text(&self, input: &'a str) -> IResult<&'a str, &'a str> { + if input.len() == 0 { + return context("Input is empty", fail)(input); + } + is_not("<")(input) + } + + pub fn parse_tag(&self, input: &'a str) -> IResult<&'a str, ParsedTag<'a>> { + if input.len() == 0 { + return context("Input is empty", fail)(input); + } + + let (input, name) = self.parse_start_tag(input)?; + if name.starts_with("/") { + // this is to make sure it fails if this matches with a end tag + return context("", fail)(input); + } + + let (_, (name, arguments)) = self.parse_tag_args(name)?; + let (input, text) = self.parse(input)?; + let (input, end_name) = self.parse_end_tag(input, name)?; + + if self.strict && end_name.is_none() { + cut(context("Missing end tag", fail))(input) + } else if end_name.unwrap_or(name) != name { + cut(context("Expected end tag of same type", fail))(input) + } else { + Ok(( + input, + ParsedTag { + name, + text, + arguments, + }, + )) + } + } + + pub fn parse(&self, input: &'a str) -> IResult<&'a str, Vec>> { + many0(alt(( + |input| { + let (input, str) = self.parse_text(input)?; + Ok((input, ParsedMessage::String(str))) + }, + |input| { + let (input, tag) = self.parse_tag(input)?; + Ok((input, ParsedMessage::ParsedTag(tag))) + }, + )))(input) + } + + pub fn serialize_tag<'b>( + &self, + tags: impl Iterator>, + mut builder: Option, + ) -> TextComponentBuilder { + for tag in tags { + match tag { + ParsedMessage::String(str) => { + let component = ComponentBuilder::text(*str); + if let Some(b) = builder { + builder = Some(b.extra(component.build())); + } else { + builder = Some(component); + } + } + ParsedMessage::ParsedTag(tag) => { + let iter = tag.text.iter(); + let mut component = self.serialize_tag(iter, None); + + if let Some(tagfn) = self.resolvers.get(tag.name) { + component = (tagfn)(&tag, component); + } + + if let Some(b) = builder { + builder = Some(b.extra(component.build())); + } else { + builder = Some(component); + } + } + } + } + + builder.unwrap_or_default() + } + + pub fn serialize(&self, input: &'a str) -> Result { + let (input, tag) = self + .parse(input) + .finish() + .map_err(|e| pretty_print_error(input, e))?; + Ok(self.serialize_tag(tag.iter(), Some(TextComponentBuilder::default())).build()) + } +} + +impl Default for MiniMessageParser<'_> { + fn default() -> Self { + Self::new(false) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_tag() { + const TEXT: &str = + "Hi Bold Italic Text Cool. Hello, World! Test"; + + let parser = MiniMessageParser::new(false); + match parser.serialize(TEXT) { + Ok(text) => println!("{}", text.to_string()), + Err(e) => println!("{}", e), + }; + + let parser = MiniMessageParser::new(true); + match parser.serialize(TEXT) { + Ok(text) => println!("{}", text.to_string()), + Err(e) => println!("{}", e), + }; + } +} +*/ diff --git a/src/lib/text/src/message/tags/color_resolver.rs b/src/lib/text/src/message/tags/color_resolver.rs new file mode 100644 index 00000000..813ad7f2 --- /dev/null +++ b/src/lib/text/src/message/tags/color_resolver.rs @@ -0,0 +1,63 @@ +use super::*; +use crate::*; +use phf::phf_map; + +static COLORS: phf::Map<&'static str, NamedColor> = phf_map! { + "black" => NamedColor::Black, + "dark_blue" => NamedColor::DarkBlue, + "dark_green" => NamedColor::DarkGreen, + "dark_aqua" => NamedColor::DarkAqua, + "dark_red" => NamedColor::DarkRed, + "dark_purple" => NamedColor::DarkPurple, + "gold" => NamedColor::Gold, + "gray" => NamedColor::Gray, + "grey" => NamedColor::Gray, + "dark_gray" => NamedColor::DarkGray, + "dark_grey" => NamedColor::DarkGray, + "blue" => NamedColor::Blue, + "green" => NamedColor::Green, + "aqua" => NamedColor::Aqua, + "red" => NamedColor::Red, + "light_purple" => NamedColor::LightPurple, + "yellow" => NamedColor::Yellow, + "white" => NamedColor::White, +}; + +pub struct ColorResolver; + +impl TagResolver for ColorResolver { + fn resolve( + &self, + name: &str, + builder: &mut TextComponentBuilder, + _opts: &ParserOptions, + ) -> Result<(), ResolveError> { + let name = match name { + "c" | "color" | "colour" => todo!(), // TODO: arguments + name => name, + }; + + if let Some(color) = COLORS.get(name) { + builder.color_mut(*color); + Ok(()) + } else if let Some(color) = RGBColor::from_string(name) { + builder.color_mut(Color::RGB(color)); + Ok(()) + } else { + Err(ResolveError::ExpectedType { + expected_type: "color", + expected: name.to_string(), + description: "Please use named colours or hex (#RRGGBB) colors.".to_string(), + }) + } + } + + fn can_process_tag(&self, name: &str) -> bool { + match name { + "c" | "color" | "colour" => true, + x if COLORS.contains_key(x) => true, + x if x.starts_with("#") && x.len() == 7 => true, + _ => false, + } + } +} diff --git a/src/lib/text/src/message/tags/mod.rs b/src/lib/text/src/message/tags/mod.rs new file mode 100644 index 00000000..01e68014 --- /dev/null +++ b/src/lib/text/src/message/tags/mod.rs @@ -0,0 +1,87 @@ +use crate::{ + message::{ast::ParsedTag, Context, ParserOptions, ResolveError}, + TextComponentBuilder, +}; + +pub mod color_resolver; +pub mod reset_resolver; + +pub trait TagResolver { + fn can_process(&self, tag: &ParsedTag<'_>, opts: &ParserOptions) -> bool { + let can_process = self.can_process_tag(tag.name); + /*((can_process && !opts.strict) + || (can_process && opts.strict && self.strict_allowed(tag.name))) + && (!tag.has_end_tag || (tag.has_end_tag && self.has_end_tag(tag.name)))*/ + can_process && (!opts.strict || (self.strict_allowed(tag.name) && self.has_end_tag(tag.name) == tag.has_end_tag)) + } + + fn call<'a>( + &self, + context: &mut Context<'a>, + opts: &ParserOptions, + ) -> Result { + if let Context::Parse { tag, builder } = context { + if self.can_process(tag, opts) { + let name = tag.name; + self.resolve(name, builder, opts)?; + return Ok(true); + } + } + + if let Context::Pre { tag, .. } = context { + if self.can_process(tag, opts) { + let name = tag.name; + self.preresolve(name, context, opts)?; + return Ok(true); + } + } + + Ok(false) + } + + /// called in the parse step if this resolver [can_process_tag] and if not in strict mode + /// unless strict mode is allowed [strict_allowed] which by default is strict is allowed. + /// + fn resolve( + &self, + name: &str, + _builder: &mut TextComponentBuilder, + _opts: &ParserOptions, + ) -> Result<(), ResolveError> { + Err(ResolveError::ProcessUnimplemented(name.to_string())) + } + + fn preresolve<'a>( + &self, + name: &str, + _context: &mut Context<'a>, + _opts: &ParserOptions, + ) -> Result<(), ResolveError> { + Err(ResolveError::ProcessUnimplemented(name.to_string())) + } + + fn can_process_tag(&self, _name: &str) -> bool { + true + } + + fn strict_allowed(&self, _name: &str) -> bool { + true + } + + fn has_end_tag(&self, _name: &str) -> bool { + true + } +} + +impl TagResolver for F +where + F: for<'a, 'b, 'c> Fn(&'b mut Context<'a>, &'c ParserOptions) -> Result, +{ + fn call<'a>( + &self, + context: &mut Context<'a>, + opts: &ParserOptions, + ) -> Result { + self(context, opts) + } +} diff --git a/src/lib/text/src/message/tags/reset_resolver.rs b/src/lib/text/src/message/tags/reset_resolver.rs new file mode 100644 index 00000000..af0b7e51 --- /dev/null +++ b/src/lib/text/src/message/tags/reset_resolver.rs @@ -0,0 +1,6 @@ +use super::*; +use crate::*; + +pub struct ResetResolver; + + diff --git a/src/lib/text/src/message/tests.rs b/src/lib/text/src/message/tests.rs new file mode 100644 index 00000000..9e17eb1c --- /dev/null +++ b/src/lib/text/src/message/tests.rs @@ -0,0 +1,25 @@ +use crate::message::{ast::*, Parser}; + +#[test] +fn test_parser() { + let parser = Parser::default(); + /*match parser.parse_ast(RootTag { + children: vec![Tag::Tag(ParsedTag { + name: "#b81c11", + has_end_tag: true, + children: vec![ + Tag::Literal("Hello, World! "), + Tag::Tag(ParsedTag { + name: "reset", + has_end_tag: false, + children: vec![Tag::Literal(":)")], + }), + ], + })], + }) { + Ok(builder) => { + println!("{}", builder.build().to_string()); + } + Err(e) => println!("{}", e), + }*/ +} From bea9da246d54ea0b948ea5294dff4f7f7fb32215 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:26:19 +0000 Subject: [PATCH 05/15] Spawn Player Entities (no sync) and Spawn Players on highest non air block --- src/bin/src/packet_handlers/login_process.rs | 62 +++++++++++++++++-- src/lib/core/src/transform/position.rs | 3 + src/lib/core/src/transform/rotation.rs | 6 ++ src/lib/net/src/connection.rs | 7 +++ src/lib/net/src/events.rs | 8 +++ src/lib/net/src/lib.rs | 1 + .../src/packets/outgoing/destroy_entity.rs | 17 +++++ src/lib/net/src/packets/outgoing/mod.rs | 3 + .../packets/outgoing/player_info_remove.rs | 17 +++++ .../net/src/packets/outgoing/spawn_entity.rs | 49 +++++++++++++++ .../outgoing/synchronize_player_position.rs | 17 +++++ src/lib/net/src/utils/broadcast.rs | 16 ++++- src/lib/world/src/chunk_format.rs | 9 +++ 13 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 src/lib/net/src/events.rs create mode 100644 src/lib/net/src/packets/outgoing/destroy_entity.rs create mode 100644 src/lib/net/src/packets/outgoing/player_info_remove.rs create mode 100644 src/lib/net/src/packets/outgoing/spawn_entity.rs diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index 960e73be..477a1dc6 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -25,9 +25,13 @@ use ferrumc_net_codec::encode::NetEncodeOpts; use ferrumc_state::GlobalState; use tracing::{debug, trace}; use ferrumc_net::packets::outgoing::player_info_update::{PlayerInfoUpdatePacket, PlayerInfo}; +use ferrumc_net::packets::outgoing::spawn_entity::SpawnEntityPacket; +use ferrumc_net::packets::outgoing::destroy_entity::DestroyEntitiesPacket; +use ferrumc_net::packets::outgoing::player_info_remove::PlayerInfoRemovePacket; use crate::events::PlayerStartLoginEvent; use ferrumc_events::errors::EventsError; use ferrumc_events::infrastructure::Event; +use ferrumc_net::{utils::broadcast::*, events::PlayerQuitEvent}; #[event_handler] async fn handle_login_start( @@ -124,12 +128,20 @@ async fn handle_ack_finish_configuration( *conn_state = ConnectionState::Play; + let chunk = state.world.load_chunk(0, 0, "overworld").await.ok(); + + let y = if let Some(ref chunk) = chunk { + (chunk.heightmaps.motion_blocking_height(0, 0)) as f64 + } else { + 256.0 + }; + // add components to the entity after the connection state has been set to play. // to avoid wasting resources on entities that are fetching stuff like server status etc. state .universe - .add_component::(conn_id, Position::default())? - .add_component::(conn_id, Rotation::default())? + .add_component::(conn_id, Position::new(0.0, y, 0.0))? + .add_component::(conn_id, Rotation::new(0.0, 0.0))? .add_component::(conn_id, OnGround::default())?; let mut writer = state.universe.get_mut::(conn_id)?; @@ -139,7 +151,7 @@ async fn handle_ack_finish_configuration( .await?; writer // 29 .send_packet( - &SynchronizePlayerPositionPacket::default(), // The coordinates here should be used for the center chunk. + &SynchronizePlayerPositionPacket::from_player(conn_id, state.clone())?, // The coordinates here should be used for the center chunk. &NetEncodeOpts::WithLength, ) .await?; @@ -168,17 +180,55 @@ async fn handle_ack_finish_configuration( ) .await?; + send_keep_alive(conn_id, state.clone(), &mut writer).await?; + + if let Some(ref chunk) = chunk { + writer.send_packet(&ferrumc_net::packets::outgoing::chunk_and_light_data::ChunkAndLightData::from_chunk(&chunk)?, &NetEncodeOpts::WithLength).await?; + } + + for (entity, profile) in state.universe.query::<&PlayerIdentity>() { + // spawn all players but ours in server for new connection + if entity != conn_id { + // send player info update + writer.send_packet(&PlayerInfoUpdatePacket::new(vec![ + PlayerInfo::from(&profile) + ]), &NetEncodeOpts::WithLength).await?; + // send spawn entity packet + writer.send_packet(&SpawnEntityPacket::new(conn_id, state.clone())?, &NetEncodeOpts::WithLength).await?; + } + } + + drop(writer); + + // broadcast player info update let profile = state .universe .get::(ack_finish_configuration_event.conn_id)?; - writer.send_packet(&PlayerInfoUpdatePacket::new(vec![ + + state.broadcast(&PlayerInfoUpdatePacket::new(vec![ PlayerInfo::from(&profile) - ]), &NetEncodeOpts::WithLength).await?; + ]), BroadcastOptions::default()).await?; - send_keep_alive(conn_id, state, &mut writer).await?; + // broadcast spawn entity packet for everyone online but current connection + state.broadcast(&SpawnEntityPacket::new(conn_id, state.clone())?, BroadcastOptions::default().not(vec![conn_id])).await?; Ok(ack_finish_configuration_event) } + +#[event_handler] +async fn handle_player_quit( + event: PlayerQuitEvent, + state: GlobalState, +) -> Result { + let conn_id = event.entity; + let profile = state + .universe + .get::(conn_id)?; + state.broadcast(&PlayerInfoRemovePacket::new(vec![profile.uuid]), BroadcastOptions::default()).await?; + state.broadcast(&DestroyEntitiesPacket::new(vec![conn_id]), BroadcastOptions::default().not(vec![conn_id])).await?; + Ok(event) +} + async fn send_keep_alive( conn_id: usize, state: GlobalState, diff --git a/src/lib/core/src/transform/position.rs b/src/lib/core/src/transform/position.rs index 3b1f0fdc..922501e9 100644 --- a/src/lib/core/src/transform/position.rs +++ b/src/lib/core/src/transform/position.rs @@ -1,5 +1,8 @@ use std::fmt::{Debug, Display, Formatter}; +use ferrumc_macros::NetEncode; +use std::io::Write; +#[derive(Copy, Clone, NetEncode)] pub struct Position { pub x: f64, pub y: f64, diff --git a/src/lib/core/src/transform/rotation.rs b/src/lib/core/src/transform/rotation.rs index 0513e783..770cb2d3 100644 --- a/src/lib/core/src/transform/rotation.rs +++ b/src/lib/core/src/transform/rotation.rs @@ -6,6 +6,8 @@ pub struct Rotation { } impl Rotation { + pub const TURN: f32 = (256.0 / 360.0); + pub fn new(yaw: f32, pitch: f32) -> Self { let yaw = yaw % 360.0; // Normalize yaw let pitch = pitch.clamp(-90.0, 90.0); // Clamp pitch @@ -38,6 +40,10 @@ impl Rotation { (x, y, z) } + + pub fn to_angle(&self) -> (u8, u8) { + ((self.yaw * Self::TURN) as u8, (self.pitch * Self::TURN) as u8) + } } impl Default for Rotation { diff --git a/src/lib/net/src/connection.rs b/src/lib/net/src/connection.rs index 73cc15a5..ca6e8f07 100644 --- a/src/lib/net/src/connection.rs +++ b/src/lib/net/src/connection.rs @@ -11,6 +11,8 @@ use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; use tokio::net::TcpStream; use tokio::time::timeout; use tracing::{debug, debug_span, trace, warn, Instrument}; +use crate::events::PlayerQuitEvent; +use ferrumc_events::infrastructure::Event; #[derive(Debug)] pub struct ConnectionControl { @@ -184,6 +186,11 @@ pub async fn handle_connection(state: Arc, tcp_stream: TcpStream) - debug!("Connection closed for entity: {:?}", entity); + let _ = PlayerQuitEvent::trigger( + PlayerQuitEvent { entity }, + state.clone() + ).await; // dont care about the result + // Remove all components from the entity // Wait until anything that might be using the entity is done diff --git a/src/lib/net/src/events.rs b/src/lib/net/src/events.rs new file mode 100644 index 00000000..2305490e --- /dev/null +++ b/src/lib/net/src/events.rs @@ -0,0 +1,8 @@ +use ferrumc_macros::Event; +use ferrumc_ecs::entities::Entity; + +#[derive(Event, Clone)] +pub struct PlayerQuitEvent { + /// The entity that this event was fired for. + pub entity: Entity, +} diff --git a/src/lib/net/src/lib.rs b/src/lib/net/src/lib.rs index dcea638d..d202ca3b 100644 --- a/src/lib/net/src/lib.rs +++ b/src/lib/net/src/lib.rs @@ -8,6 +8,7 @@ pub mod errors; pub mod packets; pub mod server; pub mod utils; +pub mod events; pub type NetResult = Result; bake_packet_registry!("\\src\\packets\\incoming"); diff --git a/src/lib/net/src/packets/outgoing/destroy_entity.rs b/src/lib/net/src/packets/outgoing/destroy_entity.rs new file mode 100644 index 00000000..65e4bab0 --- /dev/null +++ b/src/lib/net/src/packets/outgoing/destroy_entity.rs @@ -0,0 +1,17 @@ +use ferrumc_net_codec::net_types::{var_int::VarInt, length_prefixed_vec::LengthPrefixedVec}; +use ferrumc_macros::{packet, NetEncode}; +use std::io::Write; + +#[derive(NetEncode)] +#[packet(packet_id = 0x42)] +pub struct DestroyEntitiesPacket { + pub entity_ids: LengthPrefixedVec, +} + +impl DestroyEntitiesPacket { + pub fn new(ids: Vec) -> Self { + Self { + entity_ids: LengthPrefixedVec::new(ids.into_iter().map(VarInt::from).collect()), + } + } +} diff --git a/src/lib/net/src/packets/outgoing/mod.rs b/src/lib/net/src/packets/outgoing/mod.rs index 8a334cf2..6a5d5e75 100644 --- a/src/lib/net/src/packets/outgoing/mod.rs +++ b/src/lib/net/src/packets/outgoing/mod.rs @@ -15,4 +15,7 @@ pub mod status_response; pub mod synchronize_player_position; pub mod update_time; pub mod player_info_update; +pub mod player_info_remove; pub mod client_bound_plugin_message; +pub mod spawn_entity; +pub mod destroy_entity; diff --git a/src/lib/net/src/packets/outgoing/player_info_remove.rs b/src/lib/net/src/packets/outgoing/player_info_remove.rs new file mode 100644 index 00000000..30c7f0f7 --- /dev/null +++ b/src/lib/net/src/packets/outgoing/player_info_remove.rs @@ -0,0 +1,17 @@ +use ferrumc_net_codec::net_types::length_prefixed_vec::LengthPrefixedVec; +use ferrumc_macros::{packet, NetEncode}; +use std::io::Write; + +#[derive(NetEncode)] +#[packet(packet_id = 0x3D)] +pub struct PlayerInfoRemovePacket { + pub player_uuids: LengthPrefixedVec, +} + +impl PlayerInfoRemovePacket { + pub fn new(uuids: Vec) -> Self { + Self { + player_uuids: LengthPrefixedVec::new(uuids), + } + } +} diff --git a/src/lib/net/src/packets/outgoing/spawn_entity.rs b/src/lib/net/src/packets/outgoing/spawn_entity.rs new file mode 100644 index 00000000..2ea317c1 --- /dev/null +++ b/src/lib/net/src/packets/outgoing/spawn_entity.rs @@ -0,0 +1,49 @@ +use crate::{NetResult, utils::ecs_helpers::EntityExt}; +use ferrumc_state::GlobalState; +use ferrumc_core::{ + identity::player_identity::PlayerIdentity, + transform::{position::Position, rotation::Rotation} +}; +use ferrumc_net_codec::net_types::var_int::VarInt; +use ferrumc_macros::{packet, NetEncode}; +use std::io::Write; + +#[derive(NetEncode)] +#[packet(packet_id = 0x01)] +pub struct SpawnEntityPacket { + pub entity_id: VarInt, + pub entity_uuid: u128, + pub entity_type: VarInt, + pub position: Position, + pub pitch: u8, + pub yaw: u8, + pub head_yaw: u8, + pub data: VarInt, + pub velocity_x: u16, + pub velocity_y: u16, + pub velocity_z: u16, + +} + +impl SpawnEntityPacket { + pub fn new(id: usize, state: GlobalState) -> NetResult { + // only spawn players for now + let identity = id.get::(&state)?; + let position = id.get::(&state)?; + let (yaw, pitch) = id.get::(&state)?.to_angle(); + + Ok(Self { + entity_id: VarInt::from(id), + entity_uuid: identity.uuid, + entity_type: VarInt::from(128), // hardcoded for now + position: *position, + pitch, + yaw, + head_yaw: yaw, + data: VarInt::new(0), + velocity_x: 0u16, + velocity_y: 0u16, + velocity_z: 0u16, + }) + } +} diff --git a/src/lib/net/src/packets/outgoing/synchronize_player_position.rs b/src/lib/net/src/packets/outgoing/synchronize_player_position.rs index 3e0fb3a3..0e97a808 100644 --- a/src/lib/net/src/packets/outgoing/synchronize_player_position.rs +++ b/src/lib/net/src/packets/outgoing/synchronize_player_position.rs @@ -1,6 +1,9 @@ use crate::packets::outgoing::set_default_spawn_position::DEFAULT_SPAWN_POSITION; +use crate::{NetResult, utils::ecs_helpers::EntityExt}; use ferrumc_macros::{packet, NetEncode}; +use ferrumc_state::GlobalState; use ferrumc_net_codec::net_types::var_int::VarInt; +use ferrumc_core::transform::{rotation::Rotation, position::Position}; use std::io::Write; #[derive(NetEncode)] @@ -50,4 +53,18 @@ impl SynchronizePlayerPositionPacket { teleport_id, } } + + pub fn from_player(id: usize, state: GlobalState) -> NetResult { + let pos = id.get::(&state.clone())?; + let rot = id.get::(&state.clone())?; + Ok(Self::new( + pos.x, + pos.y, + pos.z, + rot.yaw, + rot.pitch, + 0, + VarInt::new(0), + )) + } } diff --git a/src/lib/net/src/utils/broadcast.rs b/src/lib/net/src/utils/broadcast.rs index 5aff8f22..a15411da 100644 --- a/src/lib/net/src/utils/broadcast.rs +++ b/src/lib/net/src/utils/broadcast.rs @@ -17,6 +17,7 @@ type SyncCallbackFn = Box; #[derive(Default)] pub struct BroadcastOptions { pub only_entities: Option>, + pub not_entities: Option>, pub async_callback: Option, pub sync_callback: Option, } @@ -27,6 +28,11 @@ impl BroadcastOptions { self } + pub fn not(mut self, entities: Vec) -> Self { + self.not_entities = Some(entities); + self + } + pub fn all(mut self) -> Self { self.only_entities = None; self @@ -61,7 +67,13 @@ pub async fn broadcast( .get_component_manager() .get_entities_with::(), Some(entities) => entities, - }; + }.into_iter() + .filter(move |entity| { + match opts.not_entities.as_ref() { + Some(vec) => !vec.contains(entity), + None => true, + } + }); // Pre-encode the packet to save resources. let packet = { @@ -74,7 +86,7 @@ pub async fn broadcast( let (state, packet, async_callback, sync_callback) = (state, packet, opts.async_callback, opts.sync_callback); - futures::stream::iter(entities.into_iter()) + futures::stream::iter(entities) .fold( (state, packet, async_callback, sync_callback), move |(state, packet, async_callback, sync_callback), entity| { diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index ea6210b5..de828486 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -92,6 +92,15 @@ impl Heightmaps { world_surface: vec![], } } + + pub fn motion_blocking_height(&self, x: usize, z: usize) -> i64 { + let bits_per_value = 9; // ceil(log2(383 + 1)) + let index = ((z % 16) * 16) + (x % 16); + let start = (index * bits_per_value) / 64; + let offset = (index * bits_per_value) % 64; + let value = self.motion_blocking[start] >> offset; + ((value & ((1 << bits_per_value) - 1)) as i64) - 64 + } } impl Default for Heightmaps { From 3446c04e38345f039bda9048ea333bfc1ecc6e2a Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:36:45 +0000 Subject: [PATCH 06/15] Revert "test" This reverts commit 13bf0b5228aa6a07652b09bf643b290ae9ea1c7e. --- src/lib/text/src/message/ast.rs | 21 - src/lib/text/src/message/errors.rs | 96 ----- src/lib/text/src/message/mod.rs | 381 ------------------ .../text/src/message/tags/color_resolver.rs | 63 --- src/lib/text/src/message/tags/mod.rs | 87 ---- .../text/src/message/tags/reset_resolver.rs | 6 - src/lib/text/src/message/tests.rs | 25 -- 7 files changed, 679 deletions(-) delete mode 100644 src/lib/text/src/message/ast.rs delete mode 100644 src/lib/text/src/message/errors.rs delete mode 100644 src/lib/text/src/message/mod.rs delete mode 100644 src/lib/text/src/message/tags/color_resolver.rs delete mode 100644 src/lib/text/src/message/tags/mod.rs delete mode 100644 src/lib/text/src/message/tags/reset_resolver.rs delete mode 100644 src/lib/text/src/message/tests.rs diff --git a/src/lib/text/src/message/ast.rs b/src/lib/text/src/message/ast.rs deleted file mode 100644 index 9f8d7a70..00000000 --- a/src/lib/text/src/message/ast.rs +++ /dev/null @@ -1,21 +0,0 @@ -// - empty text component -// test - literal -// - tag -// test - literal -// -// hello - literal -// -pub struct RootTag<'a> { - pub children: Vec>, -} - -pub struct ParsedTag<'a> { - pub name: &'a str, - pub has_end_tag: bool, - pub children: Vec>, -} - -pub enum Tag<'a> { - Literal(&'a str), - Tag(ParsedTag<'a>), -} diff --git a/src/lib/text/src/message/errors.rs b/src/lib/text/src/message/errors.rs deleted file mode 100644 index 27a15c43..00000000 --- a/src/lib/text/src/message/errors.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::message::Span; -use colored::Colorize; -use nom::error::{ErrorKind, FromExternalError, ParseError}; -use std::fmt; -use thiserror::Error; - -#[derive(Debug, PartialEq, Clone)] -pub struct MessageError<'a> { - /*pub input: &'a str, - pub line: usize, - pub column_start: usize, - pu*b column_width: usize,*/ - pub span: Span<'a>, - pub source: InnerMessageError, -} - -impl<'a> ParseError> for MessageError<'a> { - fn from_error_kind(input: Span<'a>, kind: ErrorKind) -> Self { - Self { - span: input, - source: InnerMessageError::NomError(kind), - } - } - - fn append(input: Span<'a>, kind: ErrorKind, _other: Self) -> Self { - Self::from_error_kind(input, kind) - } -} - -impl<'a, E> FromExternalError, E> for MessageError<'a> { - fn from_external_error(input: Span<'a>, kind: ErrorKind, _e: E) -> Self { - Self::from_error_kind(input, kind) - } -} - -#[derive(Debug, PartialEq, Clone, Error)] -pub enum InnerMessageError { - #[error("{0:#?}")] - NomError(ErrorKind), - #[error("Unexpected end tag (expected \"\", found \"\")")] - ExpectedEndTag(&'static str, &'static str), - #[error("Missing end tag for \"<{0}>\"")] - MissingEndTag(&'static str), - #[error("Unexpected end tag for \"<{0}>\"")] - UnexpectedEndTag(&'static str), - #[error("Missing start tag for \"\"")] - MissingStartTag(&'static str), -} - -#[derive(Debug, PartialEq, Clone, Error)] -pub enum ResolveError { - #[error("Unable to parse a {expected_type} from '{expected}'. {description}")] - ExpectedType { - expected_type: &'static str, - expected: String, - description: String, - }, - #[error("Process function is unimplemented for tag resolver \"<{0}>\"")] - ProcessUnimplemented(String), - #[error("Couldn't find resolver for tag \"<{0}>\"")] - NoResolverFor(String), - #[error("{0}")] - Custom(String), - #[error("{0}")] - CustomStatic(&'static str), -} - -fn fill_carets(col: (usize, usize), caret: &str) -> String { - format!("{}{}", " ".repeat(col.0), caret.repeat(col.1)) -} - -impl fmt::Display for MessageError<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let line = self.span.location_line().to_string(); - let space = " ".repeat(std::cmp::max(5 - (2 + line.len()), 0)); - let ln = " |".blue().to_string(); - write!( - f, - "{}: - \r {ln}{space} - \r {ln}{space}{} - \r{num}{} - \r {ln}{space} - ", - "error".bright_red(), - self.span.fragment(), - format!( - "{} {}", - fill_carets((self.span.get_column(), self.span.location_offset()), "^"), - self.source - ) - .bright_red(), - num = line.blue().to_string() + &ln + &space - ) - } -} diff --git a/src/lib/text/src/message/mod.rs b/src/lib/text/src/message/mod.rs deleted file mode 100644 index 076803de..00000000 --- a/src/lib/text/src/message/mod.rs +++ /dev/null @@ -1,381 +0,0 @@ -use nom_locate::LocatedSpan; - -pub(crate) use crate::*; -pub mod ast; -pub mod tags; - -mod errors; - -#[cfg(test)] -mod tests; - -pub use errors::*; - -use ast::*; -use tags::*; - -use nom::{ - bytes::complete::{tag, is_not}, - character::complete::char, - sequence::{tuple, delimited}, - multi::{many0, fold_many0}, - Err, Needed, -}; - -pub type Span<'a> = LocatedSpan<&'a str>; -pub type IResult<'a, O> = nom::IResult, O, MessageError<'a>>; - -#[derive(Default)] -pub struct ParserOptions { - pub strict: bool, -} - -pub enum Context<'a> { - Pre { - tag: ParsedTag<'a>, - clear_stack: bool, - }, - Parse { - builder: &'a mut TextComponentBuilder, - tag: &'a ParsedTag<'a>, - }, -} - -pub struct Parser { - resolvers: Vec>, - opts: ParserOptions, -} - -impl Parser { - pub fn new(opts: ParserOptions) -> Self { - Self { - resolvers: vec![Box::new(color_resolver::ColorResolver)], - opts, - } - } - - pub fn resolve<'a>(&self, context: &mut Context<'a>) -> Result<(), ResolveError> { - for resolver in &self.resolvers { - if resolver.call(context, &self.opts)? { - return Ok(()); - } - } - - match context { - Context::Parse { tag, .. } => Err(ResolveError::NoResolverFor(tag.name.to_string())), - Context::Pre { tag, .. } => Err(ResolveError::NoResolverFor(tag.name.to_string())), - } - } - - fn parse_ast_tag(&self, tag: &Tag) -> Result { - match tag { - Tag::Literal(literal) => Ok(TextComponentBuilder::new(*literal)), - Tag::Tag(tag) => { - let mut builder = TextComponentBuilder::new(""); - self.resolve(&mut Context::Parse { - builder: &mut builder, - tag, - })?; - - for child in &tag.children { - builder.extra_mut(self.parse_ast_tag(&child)?); - } - - Ok(builder) - } - } - } - - pub fn parse_ast(&self, ast: RootTag) -> Result { - let mut builder = TextComponentBuilder::new(""); - for child in ast.children { - builder.extra_mut(self.parse_ast_tag(&child)?); - } - - Ok(builder) - } - - pub fn parse_tag<'a>(&self, input: Span<'a>) -> IResult> { - if i.len() < 3 { - return Err(Err::Incomplete(Needed::new(3))); - } - - let (input, name) = delimited(char('<'), is_not(">"), char('>'))(input)?; - - let tag = ParsedTag { - name, - has_end_tag: false, - children: Vec::with_capacity(4), - }; - let context = Context::Pre { - tag, - clear_stack: false, - }; - - Ok((input, context)) - } - - pub fn deserialize<'a>(&self, input: Span<'a>) -> IResult> { - let mut root = RootTag { children: Vec::with_capacity(4) }; - //let mut stack: Vec> = Vec::with_capacity(16); - let (input, left): (Span<'a>, Vec>) = fold_many0( - move |input| { - let mut context = self.parse_tag(input)?; - self.resolve(&mut context)?; - context - }, - || Vec::with_capacity(16), - |mut acc: Vec<_>, item| { - if let Context::Pre { .., clear_stack } = item { - if clear_stack { - let prev_node = None; - while let Some(mut node) = acc.pop() { - if let Some(Context::Pre { tag, .. }) = prev_node.as_mut() { - prev.children.push(tag); - } - - prev_node = Some(node); - } - - if let Some(Context::Pre { tag, .. }) = prev_node { - root.children.push(prev_node); - } - } - } else { - acc.push(item); - } - acc - } - )(input)?; - - if left.len() > 0 { - left.clear(); - } - - Ok((input, root)) - } -} - -impl Default for Parser { - fn default() -> Self { - Self::new(ParserOptions::default()) - } -} - -/* -use crate::*; -use nom::{ - branch::alt, - bytes::complete::{is_not, tag, tag_no_case, take_until}, - character::complete::char, - combinator::{cut, fail, opt}, - error::{context, convert_error, ErrorKind, VerboseError, VerboseErrorKind}, - multi::{many0, fold_many0}, - sequence::{delimited, preceded, tuple}, - Finish, -}; - -type IResult = nom::IResult>; - -fn pretty_print_error(s: &str, mut e: VerboseError<&str>) -> String { - let (_root_s, root_error) = e.errors[0].clone(); - if matches!(root_error, VerboseErrorKind::Nom(ErrorKind::Fail)) - || matches!(root_error, VerboseErrorKind::Nom(ErrorKind::Eof)) - { - e.errors.remove(0); - } - convert_error(s, e) -} - -#[derive(Clone, Debug)] -pub struct ParsedTag<'a> { - pub name: &'a str, - pub text: Vec>, - pub arguments: Option<&'a str>, -} - -#[derive(Clone, Debug)] -pub enum ParsedMessage<'a> { - String(&'a str), - ParsedTag(ParsedTag<'a>), -} - -pub struct MiniMessageParser<'a> { - resolvers: HashMap< - &'a str, - Box Fn(&'b ParsedTag<'b>, TextComponentBuilder) -> TextComponentBuilder>, - >, - strict: bool, -} - -impl<'a> MiniMessageParser<'a> { - pub fn new(strict: bool) -> Self { - Self { - resolvers: HashMap::from([ - ( - "bold", - Box::new(|_: &ParsedTag<'_>, builder: TextComponentBuilder| builder.bold()) - as Box<_>, - ), - ( - "italic", - Box::new(|_: &ParsedTag<'_>, builder: TextComponentBuilder| builder.italic()) - as Box<_>, - ), - ( - "strikethrough", - Box::new(|_: &ParsedTag<'_>, builder: TextComponentBuilder| builder.strikethrough()) - as Box<_>, - ), - ]), - strict, - } - } - - pub fn parse_start_tag(&self, input: &'a str) -> IResult<&'a str, &'a str> { - delimited(tag("<"), is_not(">"), tag(">"))(input) - } - - pub fn parse_end_tag( - &self, - input: &'a str, - name: &'a str, - ) -> IResult<&'a str, Option<&'a str>> { - opt(delimited(tag("")))(input) - } - - pub fn parse_tag_values() -> impl FnMut(&'a str) -> IResult<&'a str, Option<&'a str>> { - preceded(char(':'), opt(take_until(">"))) - } - - pub fn parse_tag_args( - &self, - input: &'a str, - ) -> IResult<&'a str, (&'a str, Option<&'a str>)> { - tuple((take_until(":"), Self::parse_tag_values()))(input).or(Ok((input, (input, None)))) - } - - pub fn parse_text(&self, input: &'a str) -> IResult<&'a str, &'a str> { - if input.len() == 0 { - return context("Input is empty", fail)(input); - } - is_not("<")(input) - } - - pub fn parse_tag(&self, input: &'a str) -> IResult<&'a str, ParsedTag<'a>> { - if input.len() == 0 { - return context("Input is empty", fail)(input); - } - - let (input, name) = self.parse_start_tag(input)?; - if name.starts_with("/") { - // this is to make sure it fails if this matches with a end tag - return context("", fail)(input); - } - - let (_, (name, arguments)) = self.parse_tag_args(name)?; - let (input, text) = self.parse(input)?; - let (input, end_name) = self.parse_end_tag(input, name)?; - - if self.strict && end_name.is_none() { - cut(context("Missing end tag", fail))(input) - } else if end_name.unwrap_or(name) != name { - cut(context("Expected end tag of same type", fail))(input) - } else { - Ok(( - input, - ParsedTag { - name, - text, - arguments, - }, - )) - } - } - - pub fn parse(&self, input: &'a str) -> IResult<&'a str, Vec>> { - many0(alt(( - |input| { - let (input, str) = self.parse_text(input)?; - Ok((input, ParsedMessage::String(str))) - }, - |input| { - let (input, tag) = self.parse_tag(input)?; - Ok((input, ParsedMessage::ParsedTag(tag))) - }, - )))(input) - } - - pub fn serialize_tag<'b>( - &self, - tags: impl Iterator>, - mut builder: Option, - ) -> TextComponentBuilder { - for tag in tags { - match tag { - ParsedMessage::String(str) => { - let component = ComponentBuilder::text(*str); - if let Some(b) = builder { - builder = Some(b.extra(component.build())); - } else { - builder = Some(component); - } - } - ParsedMessage::ParsedTag(tag) => { - let iter = tag.text.iter(); - let mut component = self.serialize_tag(iter, None); - - if let Some(tagfn) = self.resolvers.get(tag.name) { - component = (tagfn)(&tag, component); - } - - if let Some(b) = builder { - builder = Some(b.extra(component.build())); - } else { - builder = Some(component); - } - } - } - } - - builder.unwrap_or_default() - } - - pub fn serialize(&self, input: &'a str) -> Result { - let (input, tag) = self - .parse(input) - .finish() - .map_err(|e| pretty_print_error(input, e))?; - Ok(self.serialize_tag(tag.iter(), Some(TextComponentBuilder::default())).build()) - } -} - -impl Default for MiniMessageParser<'_> { - fn default() -> Self { - Self::new(false) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_tag() { - const TEXT: &str = - "Hi Bold Italic Text Cool. Hello, World! Test"; - - let parser = MiniMessageParser::new(false); - match parser.serialize(TEXT) { - Ok(text) => println!("{}", text.to_string()), - Err(e) => println!("{}", e), - }; - - let parser = MiniMessageParser::new(true); - match parser.serialize(TEXT) { - Ok(text) => println!("{}", text.to_string()), - Err(e) => println!("{}", e), - }; - } -} -*/ diff --git a/src/lib/text/src/message/tags/color_resolver.rs b/src/lib/text/src/message/tags/color_resolver.rs deleted file mode 100644 index 813ad7f2..00000000 --- a/src/lib/text/src/message/tags/color_resolver.rs +++ /dev/null @@ -1,63 +0,0 @@ -use super::*; -use crate::*; -use phf::phf_map; - -static COLORS: phf::Map<&'static str, NamedColor> = phf_map! { - "black" => NamedColor::Black, - "dark_blue" => NamedColor::DarkBlue, - "dark_green" => NamedColor::DarkGreen, - "dark_aqua" => NamedColor::DarkAqua, - "dark_red" => NamedColor::DarkRed, - "dark_purple" => NamedColor::DarkPurple, - "gold" => NamedColor::Gold, - "gray" => NamedColor::Gray, - "grey" => NamedColor::Gray, - "dark_gray" => NamedColor::DarkGray, - "dark_grey" => NamedColor::DarkGray, - "blue" => NamedColor::Blue, - "green" => NamedColor::Green, - "aqua" => NamedColor::Aqua, - "red" => NamedColor::Red, - "light_purple" => NamedColor::LightPurple, - "yellow" => NamedColor::Yellow, - "white" => NamedColor::White, -}; - -pub struct ColorResolver; - -impl TagResolver for ColorResolver { - fn resolve( - &self, - name: &str, - builder: &mut TextComponentBuilder, - _opts: &ParserOptions, - ) -> Result<(), ResolveError> { - let name = match name { - "c" | "color" | "colour" => todo!(), // TODO: arguments - name => name, - }; - - if let Some(color) = COLORS.get(name) { - builder.color_mut(*color); - Ok(()) - } else if let Some(color) = RGBColor::from_string(name) { - builder.color_mut(Color::RGB(color)); - Ok(()) - } else { - Err(ResolveError::ExpectedType { - expected_type: "color", - expected: name.to_string(), - description: "Please use named colours or hex (#RRGGBB) colors.".to_string(), - }) - } - } - - fn can_process_tag(&self, name: &str) -> bool { - match name { - "c" | "color" | "colour" => true, - x if COLORS.contains_key(x) => true, - x if x.starts_with("#") && x.len() == 7 => true, - _ => false, - } - } -} diff --git a/src/lib/text/src/message/tags/mod.rs b/src/lib/text/src/message/tags/mod.rs deleted file mode 100644 index 01e68014..00000000 --- a/src/lib/text/src/message/tags/mod.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::{ - message::{ast::ParsedTag, Context, ParserOptions, ResolveError}, - TextComponentBuilder, -}; - -pub mod color_resolver; -pub mod reset_resolver; - -pub trait TagResolver { - fn can_process(&self, tag: &ParsedTag<'_>, opts: &ParserOptions) -> bool { - let can_process = self.can_process_tag(tag.name); - /*((can_process && !opts.strict) - || (can_process && opts.strict && self.strict_allowed(tag.name))) - && (!tag.has_end_tag || (tag.has_end_tag && self.has_end_tag(tag.name)))*/ - can_process && (!opts.strict || (self.strict_allowed(tag.name) && self.has_end_tag(tag.name) == tag.has_end_tag)) - } - - fn call<'a>( - &self, - context: &mut Context<'a>, - opts: &ParserOptions, - ) -> Result { - if let Context::Parse { tag, builder } = context { - if self.can_process(tag, opts) { - let name = tag.name; - self.resolve(name, builder, opts)?; - return Ok(true); - } - } - - if let Context::Pre { tag, .. } = context { - if self.can_process(tag, opts) { - let name = tag.name; - self.preresolve(name, context, opts)?; - return Ok(true); - } - } - - Ok(false) - } - - /// called in the parse step if this resolver [can_process_tag] and if not in strict mode - /// unless strict mode is allowed [strict_allowed] which by default is strict is allowed. - /// - fn resolve( - &self, - name: &str, - _builder: &mut TextComponentBuilder, - _opts: &ParserOptions, - ) -> Result<(), ResolveError> { - Err(ResolveError::ProcessUnimplemented(name.to_string())) - } - - fn preresolve<'a>( - &self, - name: &str, - _context: &mut Context<'a>, - _opts: &ParserOptions, - ) -> Result<(), ResolveError> { - Err(ResolveError::ProcessUnimplemented(name.to_string())) - } - - fn can_process_tag(&self, _name: &str) -> bool { - true - } - - fn strict_allowed(&self, _name: &str) -> bool { - true - } - - fn has_end_tag(&self, _name: &str) -> bool { - true - } -} - -impl TagResolver for F -where - F: for<'a, 'b, 'c> Fn(&'b mut Context<'a>, &'c ParserOptions) -> Result, -{ - fn call<'a>( - &self, - context: &mut Context<'a>, - opts: &ParserOptions, - ) -> Result { - self(context, opts) - } -} diff --git a/src/lib/text/src/message/tags/reset_resolver.rs b/src/lib/text/src/message/tags/reset_resolver.rs deleted file mode 100644 index af0b7e51..00000000 --- a/src/lib/text/src/message/tags/reset_resolver.rs +++ /dev/null @@ -1,6 +0,0 @@ -use super::*; -use crate::*; - -pub struct ResetResolver; - - diff --git a/src/lib/text/src/message/tests.rs b/src/lib/text/src/message/tests.rs deleted file mode 100644 index 9e17eb1c..00000000 --- a/src/lib/text/src/message/tests.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::message::{ast::*, Parser}; - -#[test] -fn test_parser() { - let parser = Parser::default(); - /*match parser.parse_ast(RootTag { - children: vec![Tag::Tag(ParsedTag { - name: "#b81c11", - has_end_tag: true, - children: vec![ - Tag::Literal("Hello, World! "), - Tag::Tag(ParsedTag { - name: "reset", - has_end_tag: false, - children: vec![Tag::Literal(":)")], - }), - ], - })], - }) { - Ok(builder) => { - println!("{}", builder.build().to_string()); - } - Err(e) => println!("{}", e), - }*/ -} From 22c5919cf770c4fe81293f85f0257f65b25ce1a6 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:34:40 +0000 Subject: [PATCH 07/15] I think deadlocking on join fixed --- src/bin/src/packet_handlers/login_process.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index 6b27ccf5..b3dd0091 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -207,6 +207,7 @@ async fn handle_ack_finish_configuration( let mut chunk_recv = state.universe.get_mut::(conn_id)?; chunk_recv.last_chunk = Some((pos.x as i32, pos.z as i32, String::from("overworld"))); chunk_recv.calculate_chunks().await; + drop(chunk_recv); // broadcast player info update let profile = state From 361181d1d280ae69c3fb6675a3f17019098b2fa2 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Mon, 30 Dec 2024 23:53:24 +0000 Subject: [PATCH 08/15] Remove comment on PlayerStartLoginEvent#profile --- src/bin/src/events.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/bin/src/events.rs b/src/bin/src/events.rs index 2f802395..7c8f6cb4 100644 --- a/src/bin/src/events.rs +++ b/src/bin/src/events.rs @@ -13,9 +13,5 @@ pub struct PlayerStartLoginEvent { pub entity: Entity, /// This profile can be changed and after the event is finished this will be the new profile. - /// - /// Be warned that this event can be cancelled or this field can be overriden by other listeners and - /// this could mean your profile will never be used. - /// pub profile: PlayerIdentity, } From 3bcc5d635858793f0e5fd73af28ec220902bbe14 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Tue, 31 Dec 2024 01:35:57 +0000 Subject: [PATCH 09/15] Fixed a few clippy warnings --- src/bin/src/packet_handlers/login_process.rs | 2 +- src/bin/src/velocity.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index b3dd0091..aa9ae8f2 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -182,7 +182,7 @@ async fn handle_ack_finish_configuration( send_keep_alive(conn_id, state.clone(), &mut writer).await?; if let Some(ref chunk) = chunk { - writer.send_packet(&ferrumc_net::packets::outgoing::chunk_and_light_data::ChunkAndLightData::from_chunk(&chunk)?, &NetEncodeOpts::WithLength).await?; + writer.send_packet(&ferrumc_net::packets::outgoing::chunk_and_light_data::ChunkAndLightData::from_chunk(chunk)?, &NetEncodeOpts::WithLength).await?; } // todos in this code below diff --git a/src/bin/src/velocity.rs b/src/bin/src/velocity.rs index c2d68c1f..ece15d1b 100644 --- a/src/bin/src/velocity.rs +++ b/src/bin/src/velocity.rs @@ -27,7 +27,7 @@ async fn handle_login_start( ) -> NetResult { if get_global_config().velocity.enabled { let entity = event.entity; - if let Ok(_) = entity.get::(&state) { + if entity.get::(&state).is_ok() { return Ok(event); } From efc30182eb18a0337035a5a16461d8edec5393e0 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Thu, 2 Jan 2025 01:46:11 +0000 Subject: [PATCH 10/15] Fix clippy errors --- src/lib/world/src/chunk_format.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index fd83255c..332685aa 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -99,7 +99,7 @@ impl Heightmaps { let start = (index * bits_per_value) / 64; let offset = (index * bits_per_value) % 64; let value = self.motion_blocking[start] >> offset; - ((value & ((1 << bits_per_value) - 1)) as i64) - 64 + ((value & ((1 << bits_per_value) - 1))) - 64 } } From 774815817dfcb0b0595476bb48aac2dc07d6cff9 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Thu, 2 Jan 2025 01:50:11 +0000 Subject: [PATCH 11/15] Forgot to removeextra parentheses --- src/lib/world/src/chunk_format.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/world/src/chunk_format.rs b/src/lib/world/src/chunk_format.rs index 332685aa..681dac5c 100644 --- a/src/lib/world/src/chunk_format.rs +++ b/src/lib/world/src/chunk_format.rs @@ -99,7 +99,7 @@ impl Heightmaps { let start = (index * bits_per_value) / 64; let offset = (index * bits_per_value) % 64; let value = self.motion_blocking[start] >> offset; - ((value & ((1 << bits_per_value) - 1))) - 64 + (value & ((1 << bits_per_value) - 1)) - 64 } } From 20e6054b049ba3c4043819bd34741e415d682ca3 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Thu, 2 Jan 2025 02:08:37 +0000 Subject: [PATCH 12/15] Hopefully all clippy errors are fixed --- src/lib/net/src/packets/outgoing/player_info_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/net/src/packets/outgoing/player_info_update.rs b/src/lib/net/src/packets/outgoing/player_info_update.rs index 660cc534..b1ecce42 100644 --- a/src/lib/net/src/packets/outgoing/player_info_update.rs +++ b/src/lib/net/src/packets/outgoing/player_info_update.rs @@ -72,7 +72,7 @@ impl PlayerInfoUpdatePacket { /// The packet to be sent to all already connected players when a new player joins the server pub fn new_player_join_packet(new_player_id: Entity, state: &GlobalState) -> Self { - let identity = new_player_id.get::(&state).unwrap(); + let identity = new_player_id.get::(state).unwrap(); Self::with_players(vec![PlayerInfo::from(&identity)]) } From d00232993df2a3d5e08106f0df85b36981212f60 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:08:34 +0000 Subject: [PATCH 13/15] Move a few things around --- src/bin/src/events.rs | 17 --------- src/bin/src/main.rs | 32 +--------------- src/bin/src/packet_handlers/login_process.rs | 10 +++-- src/bin/src/packet_handlers/mod.rs | 3 ++ src/bin/src/{ => packet_handlers}/velocity.rs | 14 +++++-- .../src/{ => packet_handlers}/whitelist.rs | 3 +- src/lib/net/src/connection.rs | 38 +++++++++++++++++++ 7 files changed, 61 insertions(+), 56 deletions(-) delete mode 100644 src/bin/src/events.rs rename src/bin/src/{ => packet_handlers}/velocity.rs (92%) rename src/bin/src/{ => packet_handlers}/whitelist.rs (89%) diff --git a/src/bin/src/events.rs b/src/bin/src/events.rs deleted file mode 100644 index 88c1ada7..00000000 --- a/src/bin/src/events.rs +++ /dev/null @@ -1,17 +0,0 @@ -use ferrumc_core::identity::player_identity::*; -use ferrumc_ecs::entities::Entity; -use ferrumc_macros::Event; - -/// This event is triggered when the player attempts to log on to the server. -/// -/// Beware that not all components on the entity may be set yet this event is mostly for: -/// a custom handshaking protocol before the player logs in using login plugin messages/etc. -/// -#[derive(Event, Clone)] -pub struct PlayerStartLoginEvent { - /// The entity that this event was fired for. - pub entity: Entity, - - /// This profile can be changed and after the event is finished this will be the new profile. - pub profile: PlayerIdentity, -} diff --git a/src/bin/src/main.rs b/src/bin/src/main.rs index 0488297e..1a83587b 100644 --- a/src/bin/src/main.rs +++ b/src/bin/src/main.rs @@ -8,13 +8,10 @@ use clap::Parser; use ferrumc_config::statics::get_global_config; use ferrumc_config::whitelist::create_whitelist; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; -use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_ecs::Universe; use ferrumc_general_purpose::paths::get_root_path; -use ferrumc_net::packets::outgoing::login_success::LoginSuccessPacket; use ferrumc_net::server::create_server_listener; -use ferrumc_net::{connection::StreamWriter, NetResult}; -use ferrumc_net_codec::encode::NetEncodeOpts; +use ferrumc_net::connection::StreamWriter; use ferrumc_state::ServerState; use ferrumc_world::World; use std::hash::{Hash, Hasher}; @@ -27,35 +24,8 @@ pub(crate) mod errors; mod packet_handlers; mod systems; -pub mod events; - -mod velocity; -mod whitelist; - pub type Result = std::result::Result; -pub async fn send_login_success( - state: Arc, - conn_id: usize, - identity: PlayerIdentity, -) -> NetResult<()> { - //Send a Login Success Response to further the login sequence - let mut writer = state.universe.get_mut::(conn_id)?; - - writer - .send_packet( - &LoginSuccessPacket::new(identity.clone()), - &NetEncodeOpts::WithLength, - ) - .await?; - - state - .universe - .add_component::(conn_id, identity)?; - - Ok(()) -} - #[tokio::main] async fn main() { let cli_args = CLIArgs::parse(); diff --git a/src/bin/src/packet_handlers/login_process.rs b/src/bin/src/packet_handlers/login_process.rs index 6d03b3f7..09746c8f 100644 --- a/src/bin/src/packet_handlers/login_process.rs +++ b/src/bin/src/packet_handlers/login_process.rs @@ -1,4 +1,3 @@ -use crate::events::PlayerStartLoginEvent; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_core::transform::grounded::OnGround; @@ -9,7 +8,7 @@ use ferrumc_ecs::entities::Entity; use ferrumc_events::errors::EventsError; use ferrumc_events::infrastructure::Event; use ferrumc_macros::event_handler; -use ferrumc_net::connection::{ConnectionState, StreamWriter}; +use ferrumc_net::connection::{ConnectionState, PlayerStartLoginEvent, StreamWriter}; use ferrumc_net::errors::NetError; use ferrumc_net::packets::incoming::ack_finish_configuration::AckFinishConfigurationEvent; use ferrumc_net::packets::incoming::keep_alive::IncomingKeepAlivePacket; @@ -55,7 +54,12 @@ async fn handle_login_start( Err(NetError::EventsError(EventsError::Cancelled)) => Ok(login_start_event), Ok(event) => { // Add the player identity component to the ECS for the entity. - crate::send_login_success(state, login_start_event.conn_id, event.profile).await?; + ferrumc_net::connection::send_login_success( + state, + login_start_event.conn_id, + event.profile, + ) + .await?; Ok(login_start_event) } e => e.map(|_| login_start_event), diff --git a/src/bin/src/packet_handlers/mod.rs b/src/bin/src/packet_handlers/mod.rs index 21fee1f7..7c2d6380 100644 --- a/src/bin/src/packet_handlers/mod.rs +++ b/src/bin/src/packet_handlers/mod.rs @@ -4,3 +4,6 @@ mod login_process; mod player; mod player_leave; mod tick_handler; + +mod velocity; +mod whitelist; diff --git a/src/bin/src/velocity.rs b/src/bin/src/packet_handlers/velocity.rs similarity index 92% rename from src/bin/src/velocity.rs rename to src/bin/src/packet_handlers/velocity.rs index de34dcf8..263c679e 100644 --- a/src/bin/src/velocity.rs +++ b/src/bin/src/packet_handlers/velocity.rs @@ -1,4 +1,3 @@ -use crate::events::*; use ferrumc_config::statics::get_global_config; use ferrumc_core::identity::player_identity::PlayerIdentity; use ferrumc_events::{errors::EventsError, infrastructure::Event}; @@ -6,7 +5,11 @@ use ferrumc_macros::event_handler; use ferrumc_net::packets::incoming::server_bound_plugin_message::*; use ferrumc_net::packets::outgoing::client_bound_plugin_message::*; use ferrumc_net::utils::ecs_helpers::EntityExt; -use ferrumc_net::{connection::StreamWriter, errors::NetError, NetResult}; +use ferrumc_net::{ + connection::{PlayerStartLoginEvent, StreamWriter}, + errors::NetError, + NetResult, +}; use ferrumc_net_codec::decode::NetDecode; use ferrumc_net_codec::{decode::NetDecodeOpts, encode::NetEncodeOpts, net_types::var_int::VarInt}; use ferrumc_state::GlobalState; @@ -118,7 +121,12 @@ async fn handle_velocity_response( .universe .remove_component::(event.entity)?; - crate::send_login_success(state.clone(), event.entity, e.profile).await?; + ferrumc_net::connection::send_login_success( + state.clone(), + event.entity, + e.profile, + ) + .await?; Ok(event) } diff --git a/src/bin/src/whitelist.rs b/src/bin/src/packet_handlers/whitelist.rs similarity index 89% rename from src/bin/src/whitelist.rs rename to src/bin/src/packet_handlers/whitelist.rs index cca5bd49..87631f72 100644 --- a/src/bin/src/whitelist.rs +++ b/src/bin/src/packet_handlers/whitelist.rs @@ -1,7 +1,6 @@ -use crate::events::*; use ferrumc_config::statics::{get_global_config, get_whitelist}; use ferrumc_macros::event_handler; -use ferrumc_net::{errors::NetError, NetResult}; +use ferrumc_net::{connection::PlayerStartLoginEvent, errors::NetError, NetResult}; use ferrumc_state::GlobalState; use ferrumc_text::*; diff --git a/src/lib/net/src/connection.rs b/src/lib/net/src/connection.rs index 58266005..5b0e9d29 100644 --- a/src/lib/net/src/connection.rs +++ b/src/lib/net/src/connection.rs @@ -1,7 +1,10 @@ use crate::errors::NetError; use crate::packets::incoming::packet_skeleton::PacketSkeleton; +use crate::packets::outgoing::login_success::LoginSuccessPacket; use crate::utils::state::TerminateConnectionPlayerExt; use crate::{handle_packet, packets::outgoing::disconnect::DISCONNECT_STRING, NetResult}; +use ferrumc_core::identity::player_identity::PlayerIdentity; +use ferrumc_ecs::entities::Entity; use ferrumc_events::infrastructure::Event; use ferrumc_macros::Event; use ferrumc_net_codec::encode::NetEncode; @@ -101,6 +104,27 @@ impl Default for CompressionStatus { } } +pub async fn send_login_success( + state: Arc, + conn_id: usize, + identity: PlayerIdentity, +) -> NetResult<()> { + let mut writer = state.universe.get_mut::(conn_id)?; + + writer + .send_packet( + &LoginSuccessPacket::new(identity.clone()), + &NetEncodeOpts::WithLength, + ) + .await?; + + state + .universe + .add_component::(conn_id, identity)?; + + Ok(()) +} + pub async fn handle_connection(state: Arc, tcp_stream: TcpStream) -> NetResult<()> { let (mut reader, writer) = tcp_stream.into_split(); @@ -210,6 +234,20 @@ pub struct PlayerDisconnectEvent { pub entity_id: usize, } +/// This event is triggered when the player attempts to log on to the server. +/// +/// Beware that not all components on the entity may be set yet this event is mostly for: +/// a custom handshaking protocol before the player logs in using login plugin messages/etc. +/// +#[derive(Event, Clone)] +pub struct PlayerStartLoginEvent { + /// The entity that this event was fired for. + pub entity: Entity, + + /// This profile can be changed and after the event is finished this will be the new profile. + pub profile: PlayerIdentity, +} + /// Since parking_lot is single-threaded, we use spawn_blocking to remove all components from the entity asynchronously (on another thread). async fn remove_all_components_blocking(state: Arc, entity: usize) -> NetResult<()> { let res = From 6e5db56faccf1840c888a80aba5d11845fade3e2 Mon Sep 17 00:00:00 2001 From: GStudiosX2 <76548041+GStudiosX2@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:12:15 +0000 Subject: [PATCH 14/15] Fix code formatting --- src/bin/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/src/main.rs b/src/bin/src/main.rs index 1a83587b..53720123 100644 --- a/src/bin/src/main.rs +++ b/src/bin/src/main.rs @@ -10,8 +10,8 @@ use ferrumc_config::whitelist::create_whitelist; use ferrumc_core::chunks::chunk_receiver::ChunkReceiver; use ferrumc_ecs::Universe; use ferrumc_general_purpose::paths::get_root_path; -use ferrumc_net::server::create_server_listener; use ferrumc_net::connection::StreamWriter; +use ferrumc_net::server::create_server_listener; use ferrumc_state::ServerState; use ferrumc_world::World; use std::hash::{Hash, Hasher}; From d11c03efa5405b452a473765b2802a5bcc4d80c0 Mon Sep 17 00:00:00 2001 From: GStudiosX <76548041+GStudiosX2@users.noreply.github.com> Date: Sun, 5 Jan 2025 07:33:47 +0000 Subject: [PATCH 15/15] [no ci] Delete grep --- grep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 grep diff --git a/grep b/grep deleted file mode 100644 index e69de29b..00000000