Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Velocity Support #139

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1358200
feat: Velocity Support
GStudiosX2 Dec 10, 2024
cfeec4d
fix PlayerIdentity documentation
GStudiosX2 Dec 10, 2024
f94bbe8
Implement Suggestions
GStudiosX2 Dec 10, 2024
13bf0b5
test
GStudiosX2 Dec 16, 2024
bea9da2
Spawn Player Entities (no sync) and Spawn Players on highest non air …
GStudiosX2 Dec 16, 2024
3446c04
Revert "test"
GStudiosX2 Dec 16, 2024
a2da495
Hopefully fixed all the conflicts and everything still works
GStudiosX2 Dec 30, 2024
22c5919
I think deadlocking on join fixed
GStudiosX2 Dec 30, 2024
361181d
Remove comment on PlayerStartLoginEvent#profile
GStudiosX2 Dec 30, 2024
3bcc5d6
Fixed a few clippy warnings
GStudiosX2 Dec 31, 2024
b2b333f
Merge branch 'master' into feat/velocity
GStudiosX2 Dec 31, 2024
c47dfc2
Fix merge conflicts
GStudiosX2 Jan 2, 2025
66ba4c9
Merge branch 'master' into feat/velocity
GStudiosX2 Jan 2, 2025
efc3018
Fix clippy errors
GStudiosX2 Jan 2, 2025
7748158
Forgot to removeextra parentheses
GStudiosX2 Jan 2, 2025
20e6054
Hopefully all clippy errors are fixed
GStudiosX2 Jan 2, 2025
d002329
Move a few things around
GStudiosX2 Jan 2, 2025
6e5db56
Fix code formatting
GStudiosX2 Jan 2, 2025
7fa503b
FFix merge conflicts, Fix crouching with geyser
GStudiosX2 Jan 2, 2025
0e1c148
Fixed merge conflicts
GStudiosX2 Jan 5, 2025
d11c03e
[no ci] Delete grep
GStudiosX2 Jan 5, 2025
ce37083
cancelable events
GStudiosX2 Jan 12, 2025
07a442b
Merge branch 'master' into feat/velocity
GStudiosX2 Jan 19, 2025
6afbf0c
fixed problem with quote! in profiling
Sweattypalms Jan 12, 2025
8f626da
cargo fmt and fixed macro expansion for profiler
Sweattypalms Jan 16, 2025
c8713ac
dashmap -> hashmap for chunk recv
Sweattypalms Jan 17, 2025
2ae8179
fmt
Sweattypalms Jan 17, 2025
a4ee2d4
oopsie daisies
Sweattypalms Jan 17, 2025
427bb86
Fix conflicts
GStudiosX2 Jan 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .etc/example-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,8 @@ cache_ttl = 60
# How big the cache can be in kb.
cache_capacity = 20_000

whitelist = false
# Velocity configuration
[velocity]
enabled = false
# The key from forwarding.secret
secret = ""
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,18 @@ 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
toml = "0.8.19"
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
serde_derive = "1.0.210"
base64 = "0.22.1"
bitcode = "0.6.3"
bitcode_derive = "0.6.3"
toml = "0.8.19"
#bitmask-enum = "2.2.5"

# Bit manipulation
byteorder = "1.5.0"
Expand Down
5 changes: 4 additions & 1 deletion src/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@ ferrumc-macros = { workspace = true }
ferrumc-nbt = { workspace = true }
ferrumc-general-purpose = { workspace = true }
ferrumc-state = { workspace = true }
ferrumc-text = { workspace = true }

parking_lot = { workspace = true, features = ["deadlock_detection"] }
tracing = { workspace = true }
tokio = { workspace = true }
futures = { workspace = true }
async-trait = { workspace = true }
clap = { workspace = true, features = ["derive"] }
hmac = { workspace = true }
sha2 = { workspace = true }
rand = { workspace = true }
flate2 = { workspace = true }
ctor = { workspace = true }


[[bin]]
name = "ferrumc"
path = "src/main.rs"
4 changes: 2 additions & 2 deletions src/bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![forbid(unsafe_code)]
extern crate core;

use crate::cli::{CLIArgs, Command, ImportArgs};
use crate::errors::BinaryError;
use clap::Parser;
use ferrumc_config::statics::get_global_config;
Expand All @@ -16,9 +17,8 @@ use systems::definition;
use tokio::runtime::Handle;
use tracing::{error, info};

pub(crate) mod errors;
use crate::cli::{CLIArgs, Command, ImportArgs};
mod cli;
pub(crate) mod errors;
mod packet_handlers;
mod systems;

Expand Down
152 changes: 68 additions & 84 deletions src/bin/src/packet_handlers/login_process.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use ferrumc_config::statics::{get_global_config, get_whitelist};
use ferrumc_core::chunks::chunk_receiver::ChunkReceiver;
use ferrumc_core::identity::player_identity::PlayerIdentity;
use ferrumc_core::transform::grounded::OnGround;
use ferrumc_core::transform::position::Position;
use ferrumc_core::transform::rotation::Rotation;
use ferrumc_ecs::components::storage::ComponentRefMut;
use ferrumc_ecs::entities::Entity;
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;
Expand All @@ -18,9 +18,7 @@ use ferrumc_net::packets::outgoing::client_bound_known_packs::ClientBoundKnownPa
use ferrumc_net::packets::outgoing::finish_configuration::FinishConfigurationPacket;
use ferrumc_net::packets::outgoing::game_event::GameEventPacket;
use ferrumc_net::packets::outgoing::keep_alive::OutgoingKeepAlivePacket;
use ferrumc_net::packets::outgoing::login_disconnect::LoginDisconnectPacket;
use ferrumc_net::packets::outgoing::login_play::LoginPlayPacket;
use ferrumc_net::packets::outgoing::login_success::LoginSuccessPacket;
use ferrumc_net::packets::outgoing::player_info_update::PlayerInfoUpdatePacket;
use ferrumc_net::packets::outgoing::registry_data::get_registry_packets;
use ferrumc_net::packets::outgoing::set_center_chunk::SetCenterChunk;
Expand All @@ -43,49 +41,30 @@ async fn handle_login_start(
) -> Result<LoginStartEvent, NetError> {
let uuid = login_start_event.login_start_packet.uuid;
let username = login_start_event.login_start_packet.username.as_str();
let player_identity = PlayerIdentity::new(username.to_string(), uuid);
debug!("Handling login start event for user: {username}, uuid: {uuid}");

// Add the player identity component to the ECS for the entity.
state
.universe
.add_component::<PlayerIdentity>(
login_start_event.conn_id,
PlayerIdentity::new(username.to_string(), uuid),
)?
/*.add_component::<ChunkReceiver>(login_start_event.conn_id, ChunkReceiver::default())?*/;

//Send a Login Success Response to further the login sequence
let mut writer = state
.universe
.get_mut::<StreamWriter>(login_start_event.conn_id)?;

if get_global_config().whitelist {
let whitelist = get_whitelist();

if whitelist.get(&uuid).is_none() {
writer.send_packet(
LoginDisconnectPacket::new(
"{\"translate\":\"multiplayer.disconnect.not_whitelisted\"}",
),
&NetEncodeOpts::WithLength,
)?;
return Ok(login_start_event);
let event = PlayerStartLoginEvent {
entity: login_start_event.conn_id,
profile: PlayerIdentity::new(username.to_string(), uuid),
cancelled: false,
};

match PlayerStartLoginEvent::trigger(event, state.clone()).await {
Err(NetError::Kick(msg)) => Err(NetError::Kick(msg)),
Ok(event) => {
if !event.is_cancelled() {
// Add the player identity component to the ECS for the entity.
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),
}

// Add the player identity component to the ECS for the entity.
state
.universe
.add_component::<PlayerIdentity>(login_start_event.conn_id, player_identity)?;

//Send a Login Success Response to further the login sequence
writer.send_packet(
LoginSuccessPacket::new(uuid, username),
&NetEncodeOpts::WithLength,
)?;

Ok(login_start_event)
}

#[event_handler]
Expand Down Expand Up @@ -145,58 +124,66 @@ 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::<Position>(entity_id, Position::default())?
.add_component::<Rotation>(entity_id, Rotation::default())?
.add_component::<Position>(entity_id, Position::new(0.0, y, 0.0))?
.add_component::<Rotation>(entity_id, Rotation::new(0.0, 0.0))?
.add_component::<OnGround>(entity_id, OnGround::default())?
.add_component::<ChunkReceiver>(entity_id, ChunkReceiver::default())?;

let mut writer = state.universe.get_mut::<StreamWriter>(entity_id)?;

writer // 21
.send_packet(LoginPlayPacket::new(entity_id), &NetEncodeOpts::WithLength)?;
writer // 29
.send_packet(
SynchronizePlayerPositionPacket::default(), // The coordinates here should be used for the center chunk.
&NetEncodeOpts::WithLength,
)?;
writer // 37
.send_packet(
SetDefaultSpawnPositionPacket::default(), // Player specific, aka. home, bed, where it would respawn.
&NetEncodeOpts::WithLength,
)?;
writer // 38
.send_packet(
GameEventPacket::start_waiting_for_level_chunks(),
&NetEncodeOpts::WithLength,
)?;
writer // 41
.send_packet(
SetCenterChunk::new(0, 0), // TODO - Dependent on the player spawn position.
&NetEncodeOpts::WithLength,
)?;
writer // other
.send_packet(
SetRenderDistance::new(5), // TODO
&NetEncodeOpts::WithLength,
)?;
writer.send_packet(LoginPlayPacket::new(entity_id), &NetEncodeOpts::WithLength)?;
writer.send_packet(
SynchronizePlayerPositionPacket::from_player(entity_id, state.clone())?, // The coordinates here should be used for the center chunk.
&NetEncodeOpts::WithLength,
)?;
writer.send_packet(
SetDefaultSpawnPositionPacket::default(), // Player specific, aka. home, bed, where it would respawn.
&NetEncodeOpts::WithLength,
)?;
writer.send_packet(
GameEventPacket::start_waiting_for_level_chunks(),
&NetEncodeOpts::WithLength,
)?;
writer.send_packet(
SetCenterChunk::new(0, 0), // TODO - Dependent on the player spawn position.
&NetEncodeOpts::WithLength,
)?;
writer.send_packet(
SetRenderDistance::new(5), // TODO
&NetEncodeOpts::WithLength,
)?;

send_keep_alive(entity_id, &state, &mut writer).await?;

let pos = state.universe.get_mut::<Position>(entity_id)?;
let mut chunk_recv = state.universe.get_mut::<ChunkReceiver>(entity_id)?;
chunk_recv.last_chunk = Some((pos.x as i32, pos.z as i32, String::from("overworld")));
chunk_recv.calculate_chunks().await;
if let Some(ref chunk) = chunk {
writer.send_packet(ferrumc_net::packets::outgoing::chunk_and_light_data::ChunkAndLightData::from_chunk(chunk)?, &NetEncodeOpts::WithLength)?;
}
}

let pos = state.universe.get::<Position>(entity_id)?;
let mut chunk_recv = state.universe.get_mut::<ChunkReceiver>(entity_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);

player_info_update_packets(entity_id, &state).await?;
broadcast_spawn_entity_packet(entity_id, &state).await?;

Ok(ack_finish_configuration_event)
}

async fn send_keep_alive(
conn_id: usize,
state: &GlobalState,
Expand All @@ -223,12 +210,7 @@ async fn player_info_update_packets(entity_id: Entity, state: &GlobalState) -> N
let packet = PlayerInfoUpdatePacket::new_player_join_packet(entity_id, state);

let start = Instant::now();
broadcast(
&packet,
state,
BroadcastOptions::default().except([entity_id]),
)
.await?;
broadcast(&packet, state, BroadcastOptions::default().all()).await?;
trace!(
"Broadcasting player info update took: {:?}",
start.elapsed()
Expand Down Expand Up @@ -263,8 +245,10 @@ async fn broadcast_spawn_entity_packet(entity_id: Entity, state: &GlobalState) -
let writer = state.universe.get_mut::<StreamWriter>(entity_id)?;
futures::stream::iter(get_all_play_players(state))
.fold(writer, |mut writer, entity| async move {
if let Ok(packet) = SpawnEntityPacket::player(entity, state) {
let _ = writer.send_packet(packet, &NetEncodeOpts::WithLength);
if entity != entity_id {
if let Ok(packet) = SpawnEntityPacket::player(entity, state) {
let _ = writer.send_packet(packet, &NetEncodeOpts::WithLength);
}
}
writer
})
Expand Down
3 changes: 3 additions & 0 deletions src/bin/src/packet_handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ mod login_process;
mod player;
mod player_leave;
mod tick_handler;

mod velocity;
mod whitelist;
10 changes: 8 additions & 2 deletions src/bin/src/packet_handlers/player/do_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ async fn handle_player_do_action(
) -> Result<PlayerDoActionEvent, NetError> {
trace!("player just did: {:?}", event.action);

// TODO: replace this with a better system to support multiple actions
match event.action {
PlayerCommandAction::StartSneaking => {
let packet = EntityMetadataPacket::new(
Expand All @@ -26,8 +27,13 @@ async fn handle_player_do_action(
broadcast(&packet, &state, Default::default()).await?;
}
PlayerCommandAction::StopSneaking => {
let packet =
EntityMetadataPacket::new(event.entity_id, [EntityMetadata::entity_standing()]);
let packet = EntityMetadataPacket::new(
event.entity_id,
[
EntityMetadata::entity_state_none(),
EntityMetadata::entity_standing(),
],
);

broadcast(&packet, &state, Default::default()).await?;
}
Expand Down
14 changes: 12 additions & 2 deletions src/bin/src/packet_handlers/player_leave.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use ferrumc_core::identity::player_identity::PlayerIdentity;
use ferrumc_macros::event_handler;
use ferrumc_net::connection::PlayerDisconnectEvent;
use ferrumc_net::errors::NetError;
use ferrumc_net::packets::outgoing::player_info_remove::PlayerInfoRemovePacket;
use ferrumc_net::packets::outgoing::remove_entities::RemoveEntitiesPacket;
use ferrumc_net::utils::broadcast::{broadcast, BroadcastOptions};
use ferrumc_state::GlobalState;
Expand All @@ -15,10 +17,18 @@ async fn handle_player_disconnect(

info!("Player disconnected: {:?}", entity_id);

let remove_entity_packet = RemoveEntitiesPacket::from_entities([entity_id]);
{
let profile = state.universe.get::<PlayerIdentity>(entity_id)?;
broadcast(
&PlayerInfoRemovePacket::new(vec![profile.uuid]),
&state,
BroadcastOptions::default().all(),
)
.await?;
}

broadcast(
&remove_entity_packet,
&RemoveEntitiesPacket::from_entities([entity_id]),
&state,
BroadcastOptions::default().all(),
)
Expand Down
Loading
Loading