Skip to content

Commit 3d80bc4

Browse files
authored
Merge pull request #152 from ferrumc-rs/feature/see-other-players
Actual multiplayer (see others move around, crouch, and swing their arm)
2 parents 5e27c5c + 05045b4 commit 3d80bc4

27 files changed

+1000
-69
lines changed

src/bin/src/main.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use ferrumc_world::World;
1616
use std::hash::{Hash, Hasher};
1717
use std::sync::Arc;
1818
use systems::definition;
19-
use tracing::{error, info};
19+
use tracing::{error, info, trace};
2020

2121
pub(crate) mod errors;
2222
use crate::cli::{CLIArgs, Command, ImportArgs};
@@ -37,11 +37,11 @@ async fn main() {
3737
let mut hasher = std::collections::hash_map::DefaultHasher::new();
3838
std::any::TypeId::of::<ChunkReceiver>().hash(&mut hasher);
3939
let digest = hasher.finish();
40-
println!("ChunkReceiver: {:X}", digest);
40+
trace!("ChunkReceiver: {:X}", digest);
4141
let mut hasher = std::collections::hash_map::DefaultHasher::new();
4242
std::any::TypeId::of::<StreamWriter>().hash(&mut hasher);
4343
let digest = hasher.finish();
44-
println!("StreamWriter: {:X}", digest);
44+
trace!("StreamWriter: {:X}", digest);
4545
}
4646

4747
match cli_args.command {

src/bin/src/packet_handlers/login_process.rs

+129-58
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use ferrumc_core::transform::grounded::OnGround;
55
use ferrumc_core::transform::position::Position;
66
use ferrumc_core::transform::rotation::Rotation;
77
use ferrumc_ecs::components::storage::ComponentRefMut;
8+
use ferrumc_ecs::entities::Entity;
89
use ferrumc_macros::event_handler;
910
use ferrumc_net::connection::{ConnectionState, StreamWriter};
1011
use ferrumc_net::errors::NetError;
@@ -20,13 +21,19 @@ use ferrumc_net::packets::outgoing::keep_alive::OutgoingKeepAlivePacket;
2021
use ferrumc_net::packets::outgoing::login_disconnect::LoginDisconnectPacket;
2122
use ferrumc_net::packets::outgoing::login_play::LoginPlayPacket;
2223
use ferrumc_net::packets::outgoing::login_success::LoginSuccessPacket;
24+
use ferrumc_net::packets::outgoing::player_info_update::PlayerInfoUpdatePacket;
2325
use ferrumc_net::packets::outgoing::registry_data::get_registry_packets;
2426
use ferrumc_net::packets::outgoing::set_center_chunk::SetCenterChunk;
2527
use ferrumc_net::packets::outgoing::set_default_spawn_position::SetDefaultSpawnPositionPacket;
2628
use ferrumc_net::packets::outgoing::set_render_distance::SetRenderDistance;
29+
use ferrumc_net::packets::outgoing::spawn_entity::SpawnEntityPacket;
2730
use ferrumc_net::packets::outgoing::synchronize_player_position::SynchronizePlayerPositionPacket;
31+
use ferrumc_net::utils::broadcast::{broadcast, get_all_play_players, BroadcastOptions};
32+
use ferrumc_net::NetResult;
2833
use ferrumc_net_codec::encode::NetEncodeOpts;
2934
use ferrumc_state::GlobalState;
35+
use futures::StreamExt;
36+
use std::time::Instant;
3037
use tracing::{debug, trace};
3138

3239
#[event_handler]
@@ -46,7 +53,7 @@ async fn handle_login_start(
4653
login_start_event.conn_id,
4754
PlayerIdentity::new(username.to_string(), uuid),
4855
)?
49-
.add_component::<ChunkReceiver>(login_start_event.conn_id, ChunkReceiver::default())?;
56+
/*.add_component::<ChunkReceiver>(login_start_event.conn_id, ChunkReceiver::default())?*/;
5057

5158
//Send a Login Success Response to further the login sequence
5259
let mut writer = state
@@ -145,69 +152,73 @@ async fn handle_ack_finish_configuration(
145152
state: GlobalState,
146153
) -> Result<AckFinishConfigurationEvent, NetError> {
147154
trace!("Handling Ack Finish Configuration event");
155+
let entity_id = ack_finish_configuration_event.conn_id;
156+
{
157+
let mut conn_state = state.universe.get_mut::<ConnectionState>(entity_id)?;
158+
159+
*conn_state = ConnectionState::Play;
160+
161+
// add components to the entity after the connection state has been set to play.
162+
// to avoid wasting resources on entities that are fetching stuff like server status etc.
163+
state
164+
.universe
165+
.add_component::<Position>(entity_id, Position::default())?
166+
.add_component::<Rotation>(entity_id, Rotation::default())?
167+
.add_component::<OnGround>(entity_id, OnGround::default())?
168+
.add_component::<ChunkReceiver>(entity_id, ChunkReceiver::default())?;
169+
170+
let mut writer = state.universe.get_mut::<StreamWriter>(entity_id)?;
171+
172+
writer // 21
173+
.send_packet(&LoginPlayPacket::new(entity_id), &NetEncodeOpts::WithLength)
174+
.await?;
175+
writer // 29
176+
.send_packet(
177+
&SynchronizePlayerPositionPacket::default(), // The coordinates here should be used for the center chunk.
178+
&NetEncodeOpts::WithLength,
179+
)
180+
.await?;
181+
writer // 37
182+
.send_packet(
183+
&SetDefaultSpawnPositionPacket::default(), // Player specific, aka. home, bed, where it would respawn.
184+
&NetEncodeOpts::WithLength,
185+
)
186+
.await?;
187+
writer // 38
188+
.send_packet(
189+
&GameEventPacket::start_waiting_for_level_chunks(),
190+
&NetEncodeOpts::WithLength,
191+
)
192+
.await?;
193+
writer // 41
194+
.send_packet(
195+
&SetCenterChunk::new(0, 0), // TODO - Dependent on the player spawn position.
196+
&NetEncodeOpts::WithLength,
197+
)
198+
.await?;
199+
writer // other
200+
.send_packet(
201+
&SetRenderDistance::new(5), // TODO
202+
&NetEncodeOpts::WithLength,
203+
)
204+
.await?;
205+
206+
send_keep_alive(entity_id, &state, &mut writer).await?;
207+
208+
let pos = state.universe.get_mut::<Position>(entity_id)?;
209+
let mut chunk_recv = state.universe.get_mut::<ChunkReceiver>(entity_id)?;
210+
chunk_recv.last_chunk = Some((pos.x as i32, pos.z as i32, String::from("overworld")));
211+
chunk_recv.calculate_chunks().await;
212+
}
148213

149-
let conn_id = ack_finish_configuration_event.conn_id;
150-
151-
let mut conn_state = state.universe.get_mut::<ConnectionState>(conn_id)?;
152-
153-
*conn_state = ConnectionState::Play;
154-
155-
// add components to the entity after the connection state has been set to play.
156-
// to avoid wasting resources on entities that are fetching stuff like server status etc.
157-
state
158-
.universe
159-
.add_component::<Position>(conn_id, Position::default())?
160-
.add_component::<Rotation>(conn_id, Rotation::default())?
161-
.add_component::<OnGround>(conn_id, OnGround::default())?;
162-
163-
let mut writer = state.universe.get_mut::<StreamWriter>(conn_id)?;
164-
165-
writer // 21
166-
.send_packet(&LoginPlayPacket::new(conn_id), &NetEncodeOpts::WithLength)
167-
.await?;
168-
writer // 29
169-
.send_packet(
170-
&SynchronizePlayerPositionPacket::default(), // The coordinates here should be used for the center chunk.
171-
&NetEncodeOpts::WithLength,
172-
)
173-
.await?;
174-
writer // 37
175-
.send_packet(
176-
&SetDefaultSpawnPositionPacket::default(), // Player specific, aka. home, bed, where it would respawn.
177-
&NetEncodeOpts::WithLength,
178-
)
179-
.await?;
180-
writer // 38
181-
.send_packet(
182-
&GameEventPacket::start_waiting_for_level_chunks(),
183-
&NetEncodeOpts::WithLength,
184-
)
185-
.await?;
186-
writer // 41
187-
.send_packet(
188-
&SetCenterChunk::new(0, 0), // TODO - Dependent on the player spawn position.
189-
&NetEncodeOpts::WithLength,
190-
)
191-
.await?;
192-
writer // other
193-
.send_packet(
194-
&SetRenderDistance::new(5), // TODO
195-
&NetEncodeOpts::WithLength,
196-
)
197-
.await?;
198-
199-
let pos = state.universe.get_mut::<Position>(conn_id)?;
200-
let mut chunk_recv = state.universe.get_mut::<ChunkReceiver>(conn_id)?;
201-
chunk_recv.last_chunk = Some((pos.x as i32, pos.z as i32, String::from("overworld")));
202-
chunk_recv.calculate_chunks().await;
203-
204-
send_keep_alive(conn_id, state, &mut writer).await?;
214+
player_info_update_packets(entity_id, &state).await?;
215+
broadcast_spawn_entity_packet(entity_id, &state).await?;
205216

206217
Ok(ack_finish_configuration_event)
207218
}
208219
async fn send_keep_alive(
209220
conn_id: usize,
210-
state: GlobalState,
221+
state: &GlobalState,
211222
writer: &mut ComponentRefMut<'_, StreamWriter>,
212223
) -> Result<(), NetError> {
213224
let keep_alive_packet = OutgoingKeepAlivePacket::default();
@@ -226,3 +237,63 @@ async fn send_keep_alive(
226237

227238
Ok(())
228239
}
240+
241+
async fn player_info_update_packets(entity_id: Entity, state: &GlobalState) -> NetResult<()> {
242+
// Broadcasts a player info update packet to all players.
243+
{
244+
let packet = PlayerInfoUpdatePacket::new_player_join_packet(entity_id, state);
245+
246+
let start = Instant::now();
247+
broadcast(
248+
&packet,
249+
state,
250+
BroadcastOptions::default().except([entity_id]),
251+
)
252+
.await?;
253+
trace!(
254+
"Broadcasting player info update took: {:?}",
255+
start.elapsed()
256+
);
257+
}
258+
259+
// Tell the player about all the other players that are already connected.
260+
{
261+
let packet = PlayerInfoUpdatePacket::existing_player_info_packet(entity_id, state);
262+
263+
let start = Instant::now();
264+
let mut writer = state.universe.get_mut::<StreamWriter>(entity_id)?;
265+
writer
266+
.send_packet(&packet, &NetEncodeOpts::WithLength)
267+
.await?;
268+
debug!("Sending player info update took: {:?}", start.elapsed());
269+
}
270+
271+
Ok(())
272+
}
273+
274+
async fn broadcast_spawn_entity_packet(entity_id: Entity, state: &GlobalState) -> NetResult<()> {
275+
let packet = SpawnEntityPacket::player(entity_id, state)?;
276+
277+
let start = Instant::now();
278+
broadcast(
279+
&packet,
280+
state,
281+
BroadcastOptions::default().except([entity_id]),
282+
)
283+
.await?;
284+
trace!("Broadcasting spawn entity took: {:?}", start.elapsed());
285+
286+
let writer = state.universe.get_mut::<StreamWriter>(entity_id)?;
287+
futures::stream::iter(get_all_play_players(state))
288+
.fold(writer, |mut writer, entity| async move {
289+
if let Ok(packet) = SpawnEntityPacket::player(entity, state) {
290+
let _ = writer
291+
.send_packet(&packet, &NetEncodeOpts::WithLength)
292+
.await;
293+
}
294+
writer
295+
})
296+
.await;
297+
298+
Ok(())
299+
}

src/bin/src/packet_handlers/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod animations;
22
mod handshake;
33
mod login_process;
4+
mod player;
5+
mod player_leave;
46
mod tick_handler;
5-
mod transform;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use ferrumc_macros::event_handler;
2+
use ferrumc_net::errors::NetError;
3+
use ferrumc_net::packets::incoming::player_command::{PlayerCommandAction, PlayerDoActionEvent};
4+
use ferrumc_net::packets::outgoing::entity_metadata::{EntityMetadata, EntityMetadataPacket};
5+
use ferrumc_net::utils::broadcast::broadcast;
6+
use ferrumc_state::GlobalState;
7+
use tracing::trace;
8+
9+
#[event_handler]
10+
async fn handle_player_do_action(
11+
event: PlayerDoActionEvent,
12+
state: GlobalState,
13+
) -> Result<PlayerDoActionEvent, NetError> {
14+
trace!("player just did: {:?}", event.action);
15+
16+
match event.action {
17+
PlayerCommandAction::StartSneaking => {
18+
let packet = EntityMetadataPacket::new(
19+
event.entity_id,
20+
[
21+
EntityMetadata::entity_sneaking_visual(),
22+
EntityMetadata::entity_sneaking_pressed(),
23+
],
24+
);
25+
26+
broadcast(&packet, &state, Default::default()).await?;
27+
}
28+
PlayerCommandAction::StopSneaking => {
29+
let packet =
30+
EntityMetadataPacket::new(event.entity_id, [EntityMetadata::entity_standing()]);
31+
32+
broadcast(&packet, &state, Default::default()).await?;
33+
}
34+
_ => {}
35+
}
36+
37+
Ok(event)
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod do_action;
2+
pub mod see_other_players;
3+
pub mod update_player_position;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use ferrumc_core::transform::grounded::OnGround;
2+
use ferrumc_core::transform::position::Position;
3+
use ferrumc_core::transform::rotation::Rotation;
4+
use ferrumc_macros::event_handler;
5+
use ferrumc_net::errors::NetError;
6+
use ferrumc_net::packets::outgoing::set_head_rotation::SetHeadRotationPacket;
7+
use ferrumc_net::packets::outgoing::teleport_entity::TeleportEntityPacket;
8+
use ferrumc_net::packets::packet_events::TransformEvent;
9+
use ferrumc_net::utils::broadcast::{broadcast, BroadcastOptions};
10+
use ferrumc_net::utils::ecs_helpers::EntityExt;
11+
use ferrumc_net_codec::net_types::angle::NetAngle;
12+
use ferrumc_state::GlobalState;
13+
14+
#[event_handler(priority = "normal")]
15+
async fn handle_player_move(
16+
event: TransformEvent,
17+
state: GlobalState,
18+
) -> Result<TransformEvent, NetError> {
19+
let entity = event.conn_id;
20+
21+
let pos = entity.get::<Position>(&state)?;
22+
let rot = entity.get::<Rotation>(&state)?;
23+
let grounded = entity.get::<OnGround>(&state)?;
24+
25+
let teleport_packet = TeleportEntityPacket::new(entity, &pos, &rot, grounded.0);
26+
let head_rot_packet =
27+
SetHeadRotationPacket::new(entity as i32, NetAngle::from_degrees(rot.yaw as f64));
28+
29+
let start = std::time::Instant::now();
30+
broadcast(&teleport_packet, &state, BroadcastOptions::default().all()).await?;
31+
broadcast(&head_rot_packet, &state, BroadcastOptions::default().all()).await?;
32+
33+
tracing::trace!("broadcasting entity move took {:?}", start.elapsed());
34+
35+
Ok(event)
36+
}

src/bin/src/packet_handlers/transform/update_player_position.rs src/bin/src/packet_handlers/player/update_player_position.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ use ferrumc_net::utils::ecs_helpers::EntityExt;
99
use ferrumc_state::GlobalState;
1010
use tracing::trace;
1111

12-
#[event_handler]
12+
#[event_handler(priority = "fastest")]
1313
async fn handle_player_move(
1414
event: TransformEvent,
1515
state: GlobalState,
1616
) -> Result<TransformEvent, NetError> {
1717
let conn_id = event.conn_id;
18+
1819
if let Some(ref new_position) = event.position {
1920
trace!("Getting chunk_recv 1 for player move");
2021
{
@@ -43,6 +44,7 @@ async fn handle_player_move(
4344
trace!("Getting position 1 for player move");
4445
let mut position = conn_id.get_mut::<Position>(&state)?;
4546
trace!("Got position 1 for player move");
47+
4648
*position = Position::new(new_position.x, new_position.y, new_position.z);
4749
}
4850

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use ferrumc_macros::event_handler;
2+
use ferrumc_net::connection::PlayerDisconnectEvent;
3+
use ferrumc_net::errors::NetError;
4+
use ferrumc_net::packets::outgoing::remove_entities::RemoveEntitiesPacket;
5+
use ferrumc_net::utils::broadcast::{broadcast, BroadcastOptions};
6+
use ferrumc_state::GlobalState;
7+
use tracing::info;
8+
9+
#[event_handler]
10+
async fn handle_player_disconnect(
11+
event: PlayerDisconnectEvent,
12+
state: GlobalState,
13+
) -> Result<PlayerDisconnectEvent, NetError> {
14+
let entity_id = event.entity_id;
15+
16+
info!("Player disconnected: {:?}", entity_id);
17+
18+
let remove_entity_packet = RemoveEntitiesPacket::from_entities([entity_id]);
19+
20+
broadcast(
21+
&remove_entity_packet,
22+
&state,
23+
BroadcastOptions::default().all(),
24+
)
25+
.await?;
26+
27+
Ok(event)
28+
}

src/bin/src/packet_handlers/transform/mod.rs

-1
This file was deleted.

0 commit comments

Comments
 (0)