mirror of
https://github.com/timvisee/lazymc.git
synced 2025-05-19 04:40:22 -07:00
Improve lobby handling, resolve various TODOs
This commit is contained in:
parent
2cc64b29e0
commit
8b88cb16c5
123
src/lobby.rs
123
src/lobby.rs
@ -1,4 +1,5 @@
|
|||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@ -50,6 +51,9 @@ const SERVER_WARMUP: Duration = Duration::from_secs(1);
|
|||||||
/// Shown in F3 menu. Updated once client is relayed to real server.
|
/// Shown in F3 menu. Updated once client is relayed to real server.
|
||||||
const SERVER_BRAND: &[u8] = b"lazymc";
|
const SERVER_BRAND: &[u8] = b"lazymc";
|
||||||
|
|
||||||
|
/// Auto incrementing ID source for keep alive packets.
|
||||||
|
const KEEP_ALIVE_ID: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
/// Serve lobby service for given client connection.
|
/// Serve lobby service for given client connection.
|
||||||
///
|
///
|
||||||
/// The client must be in the login state, or this will error.
|
/// The client must be in the login state, or this will error.
|
||||||
@ -110,7 +114,7 @@ pub async fn serve(
|
|||||||
trace!(target: "lazymc::lobby", "Client login success, sending required play packets for lobby world");
|
trace!(target: "lazymc::lobby", "Client login success, sending required play packets for lobby world");
|
||||||
|
|
||||||
// Send packets to client required to get into workable play state for lobby world
|
// Send packets to client required to get into workable play state for lobby world
|
||||||
send_lobby_play_packets(&mut writer).await?;
|
send_lobby_play_packets(&mut writer, &server).await?;
|
||||||
|
|
||||||
// Wait for server to come online, then set up new connection to it
|
// Wait for server to come online, then set up new connection to it
|
||||||
stage_wait(server.clone(), &mut writer).await?;
|
stage_wait(server.clone(), &mut writer).await?;
|
||||||
@ -129,8 +133,9 @@ pub async fn serve(
|
|||||||
send_respawn_from_join(&mut writer, join_game).await?;
|
send_respawn_from_join(&mut writer, join_game).await?;
|
||||||
|
|
||||||
// Drain inbound connection so we don't confuse the server
|
// Drain inbound connection so we don't confuse the server
|
||||||
// TODO: can we drain everything? we might need to forward everything to server except
|
// TODO: can we void everything? we might need to forward everything to server except
|
||||||
// for some blacklisted ones
|
// for some blacklisted ones
|
||||||
|
trace!(target: "lazymc::lobby", "Voiding remaining incoming lobby client data before relay to real server");
|
||||||
drain_stream(&mut reader).await?;
|
drain_stream(&mut reader).await?;
|
||||||
|
|
||||||
// Client and server connection ready now, move client to proxy
|
// Client and server connection ready now, move client to proxy
|
||||||
@ -183,54 +188,58 @@ async fn respond_login_success(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send packets to client to get workable play state for lobby world.
|
/// Send packets to client to get workable play state for lobby world.
|
||||||
async fn send_lobby_play_packets(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
async fn send_lobby_play_packets(writer: &mut WriteHalf<'_>, server: &Server) -> Result<(), ()> {
|
||||||
// See: https://wiki.vg/Protocol_FAQ#What.27s_the_normal_login_sequence_for_a_client.3F
|
// See: https://wiki.vg/Protocol_FAQ#What.27s_the_normal_login_sequence_for_a_client.3F
|
||||||
|
|
||||||
// Send initial game join
|
// Send initial game join
|
||||||
send_lobby_join_game(writer).await?;
|
send_lobby_join_game(writer, server).await?;
|
||||||
|
|
||||||
// Send server brand
|
// Send server brand
|
||||||
send_lobby_brand(writer).await?;
|
send_lobby_brand(writer).await?;
|
||||||
|
|
||||||
// Send spawn and player position, disables 'download terrain' screen
|
// Send spawn and player position, disables 'download terrain' screen
|
||||||
|
// Note: If this screen stays, we may need to send it a second time
|
||||||
send_lobby_player_pos(writer).await?;
|
send_lobby_player_pos(writer).await?;
|
||||||
|
|
||||||
// Notify client of world time, required once before keep-alive packets
|
// Notify client of world time, required once before keep-alive packets
|
||||||
send_lobby_time_update(writer).await?;
|
send_lobby_time_update(writer).await?;
|
||||||
|
|
||||||
// TODO: we might need to send player_pos one more time
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send initial join game packet to client for lobby.
|
/// Send initial join game packet to client for lobby.
|
||||||
async fn send_lobby_join_game(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
async fn send_lobby_join_game(writer: &mut WriteHalf<'_>, server: &Server) -> Result<(), ()> {
|
||||||
// Send Minecrafts default states, slightly customised for lobby world
|
// Send Minecrafts default states, slightly customised for lobby world
|
||||||
// TODO: use values from real server here!
|
let packet = {
|
||||||
let packet = JoinGame {
|
let status = server.status();
|
||||||
// TODO: ID must not collide with any other entity, possibly send huge number
|
|
||||||
entity_id: 0,
|
JoinGame {
|
||||||
hardcore: false,
|
// Player ID must be unique, if it collides with another server entity ID the player gets
|
||||||
game_mode: 3,
|
// in a weird state and cannot move
|
||||||
previous_game_mode: -1i8 as u8,
|
entity_id: 0,
|
||||||
world_names: vec![
|
// TODO: use real server value
|
||||||
"minecraft:overworld".into(),
|
hardcore: false,
|
||||||
"minecraft:the_nether".into(),
|
game_mode: 3,
|
||||||
"minecraft:the_end".into(),
|
previous_game_mode: -1i8 as u8,
|
||||||
],
|
world_names: vec![
|
||||||
dimension_codec: snbt_to_compound_tag(include_str!("../res/dimension_codec.snbt")),
|
"minecraft:overworld".into(),
|
||||||
dimension: snbt_to_compound_tag(include_str!("../res/dimension.snbt")),
|
"minecraft:the_nether".into(),
|
||||||
// TODO: test whether using minecraft:overworld breaks?
|
"minecraft:the_end".into(),
|
||||||
world_name: "lazymc:lobby".into(),
|
],
|
||||||
hashed_seed: 0,
|
dimension_codec: snbt_to_compound_tag(include_str!("../res/dimension_codec.snbt")),
|
||||||
max_players: 20,
|
dimension: snbt_to_compound_tag(include_str!("../res/dimension.snbt")),
|
||||||
// TODO: try very low view distance?
|
world_name: "lazymc:lobby".into(),
|
||||||
view_distance: 10,
|
hashed_seed: 0,
|
||||||
// TODO: set to true!
|
max_players: status.as_ref().map(|s| s.players.max as i32).unwrap_or(20),
|
||||||
reduced_debug_info: false,
|
// TODO: use real server value
|
||||||
enable_respawn_screen: false,
|
view_distance: 10,
|
||||||
is_debug: true,
|
// TODO: use real server value
|
||||||
is_flat: false,
|
reduced_debug_info: false,
|
||||||
|
// TODO: use real server value
|
||||||
|
enable_respawn_screen: true,
|
||||||
|
is_debug: true,
|
||||||
|
is_flat: false,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
@ -304,8 +313,10 @@ async fn send_lobby_time_update(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
|||||||
///
|
///
|
||||||
/// Required periodically in play mode to prevent client timeout.
|
/// Required periodically in play mode to prevent client timeout.
|
||||||
async fn send_keep_alive(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
async fn send_keep_alive(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||||
// TODO: keep picking random ID!
|
let packet = ClientBoundKeepAlive {
|
||||||
let packet = ClientBoundKeepAlive { id: 0 };
|
// Keep sending new IDs
|
||||||
|
id: KEEP_ALIVE_ID.fetch_add(1, Ordering::Relaxed),
|
||||||
|
};
|
||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
packet.encode(&mut data).map_err(|_| ())?;
|
packet.encode(&mut data).map_err(|_| ())?;
|
||||||
@ -353,11 +364,11 @@ async fn send_lobby_title(writer: &mut WriteHalf<'_>, text: &str) -> Result<(),
|
|||||||
|
|
||||||
// Set title times
|
// Set title times
|
||||||
let packet = if title.is_empty() && subtitle.is_empty() {
|
let packet = if title.is_empty() && subtitle.is_empty() {
|
||||||
// TODO: figure out real default values here
|
// Defaults: https://minecraft.fandom.com/wiki/Commands/title#Detail
|
||||||
SetTitleTimes {
|
SetTitleTimes {
|
||||||
fade_in: 10,
|
fade_in: 10,
|
||||||
stay: 100,
|
stay: 70,
|
||||||
fade_out: 10,
|
fade_out: 20,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SetTitleTimes {
|
SetTitleTimes {
|
||||||
@ -440,14 +451,11 @@ async fn stage_wait<'a>(server: Arc<Server>, writer: &mut WriteHalf<'a>) -> Resu
|
|||||||
/// Wait for the server to come online.
|
/// Wait for the server to come online.
|
||||||
///
|
///
|
||||||
/// Returns `Ok(())` once the server is online, returns `Err(())` if waiting failed.
|
/// Returns `Ok(())` once the server is online, returns `Err(())` if waiting failed.
|
||||||
// TODO: go through this, use proper error messages
|
|
||||||
async fn wait_for_server<'a>(server: Arc<Server>) -> Result<(), ()> {
|
async fn wait_for_server<'a>(server: Arc<Server>) -> Result<(), ()> {
|
||||||
debug!(target: "lazymc::lobby", "Waiting on server...");
|
debug!(target: "lazymc::lobby", "Waiting on server...");
|
||||||
|
|
||||||
// Set up polling interval, get timeout
|
// Set up polling interval
|
||||||
let mut poll_interval = time::interval(SERVER_POLL_INTERVAL);
|
let mut poll_interval = time::interval(SERVER_POLL_INTERVAL);
|
||||||
// let since = Instant::now();
|
|
||||||
// let timeout = config.time.hold_client_for as u64;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// TODO: wait for start signal over channel instead of polling
|
// TODO: wait for start signal over channel instead of polling
|
||||||
@ -459,9 +467,6 @@ async fn wait_for_server<'a>(server: Arc<Server>) -> Result<(), ()> {
|
|||||||
// Still waiting on server start
|
// Still waiting on server start
|
||||||
State::Starting => {
|
State::Starting => {
|
||||||
trace!(target: "lazymc::lobby", "Server not ready, holding client for longer");
|
trace!(target: "lazymc::lobby", "Server not ready, holding client for longer");
|
||||||
|
|
||||||
// TODO: add timeout here?
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,6 +492,7 @@ async fn wait_for_server<'a>(server: Arc<Server>) -> Result<(), ()> {
|
|||||||
///
|
///
|
||||||
/// This will initialize the connection to the play state. Client details are used.
|
/// This will initialize the connection to the play state. Client details are used.
|
||||||
// TODO: clean this up
|
// TODO: clean this up
|
||||||
|
// TODO: add timeout
|
||||||
async fn connect_to_server(
|
async fn connect_to_server(
|
||||||
client_info: ClientInfo,
|
client_info: ClientInfo,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
@ -569,14 +575,13 @@ async fn connect_to_server(
|
|||||||
debug!(target: "lazymc::lobby", "- Packet ID: 0x{:02X} ({})", packet.id, packet.id);
|
debug!(target: "lazymc::lobby", "- Packet ID: 0x{:02X} ({})", packet.id, packet.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Gracefully close connection
|
// Gracefully close connection
|
||||||
// match writer.shutdown().await {
|
match writer.shutdown().await {
|
||||||
// Ok(_) => {}
|
Ok(_) => {}
|
||||||
// Err(err) if err.kind() == io::ErrorKind::NotConnected => {}
|
Err(err) if err.kind() == io::ErrorKind::NotConnected => {}
|
||||||
// Err(_) => return Err(()),
|
Err(_) => return Err(()),
|
||||||
// }
|
}
|
||||||
|
|
||||||
// TODO: do we ever reach this?
|
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,7 +611,6 @@ async fn wait_for_server_join_game(
|
|||||||
// Catch join game
|
// Catch join game
|
||||||
if packet.id == proto::packets::play::CLIENT_JOIN_GAME {
|
if packet.id == proto::packets::play::CLIENT_JOIN_GAME {
|
||||||
let join_game = JoinGame::decode(&mut packet.data.as_slice()).map_err(|err| {
|
let join_game = JoinGame::decode(&mut packet.data.as_slice()).map_err(|err| {
|
||||||
// TODO: remove this debug
|
|
||||||
dbg!(err);
|
dbg!(err);
|
||||||
()
|
()
|
||||||
})?;
|
})?;
|
||||||
@ -626,7 +630,6 @@ async fn wait_for_server_join_game(
|
|||||||
Err(_) => return Err(()),
|
Err(_) => return Err(()),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: will we ever reach this?
|
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,24 +653,16 @@ pub fn route_proxy(inbound: TcpStream, outbound: TcpStream, inbound_queue: Bytes
|
|||||||
tokio::spawn(service);
|
tokio::spawn(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drain given reader until nothing is left.
|
/// Drain given reader until nothing is left voiding all data.
|
||||||
// TODO: go through this, use proper error messages
|
|
||||||
async fn drain_stream<'a>(reader: &mut ReadHalf<'a>) -> Result<(), ()> {
|
async fn drain_stream<'a>(reader: &mut ReadHalf<'a>) -> Result<(), ()> {
|
||||||
// TODO: remove after debug
|
let mut drain_buf = [0; 8 * 1024];
|
||||||
trace!(target: "lazymc::lobby", "Draining stream...");
|
|
||||||
|
|
||||||
// TODO: use other size, look at default std::io size?
|
|
||||||
let mut drain_buf = [0; 1024];
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match reader.try_read(&mut drain_buf) {
|
match reader.try_read(&mut drain_buf) {
|
||||||
// TODO: stop if read < drain_buf.len() ?
|
|
||||||
Ok(read) if read == 0 => return Ok(()),
|
Ok(read) if read == 0 => return Ok(()),
|
||||||
Err(err) if err.kind() == ErrorKind::WouldBlock => return Ok(()),
|
Err(err) if err.kind() == ErrorKind::WouldBlock => return Ok(()),
|
||||||
Ok(_) => continue,
|
Ok(_) => continue,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// TODO: remove after debug
|
error!(target: "lazymc::lobby", "Failed to drain lobby client connection before relaying to real server. Maybe already disconnected? Error: {:?}", err);
|
||||||
dbg!("drain err", err);
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user