Add lobby method to configuration
This commit is contained in:
parent
4907780f7c
commit
ae6e877f17
@ -97,6 +97,33 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
|
||||
# Forwarded-to server will receive original client handshake and login request as received by lazymc.
|
||||
#address = "127.0.0.1:25565"
|
||||
|
||||
[join.lobby]
|
||||
# Lobby occupation method.
|
||||
# The client joins a fake lobby server with an empty world, floating in space.
|
||||
# A message is overlayed on screen to notify the server is starting.
|
||||
# The client will be teleported to the real server once it is ready.
|
||||
# This may keep the client occupied forever if no timeout is set.
|
||||
# Consumes client, not allowing other join methods afterwards.
|
||||
|
||||
# !!! WARNING !!!
|
||||
# This is highly experimental and unstable.
|
||||
# This may break the game and crash clients.
|
||||
# Don't enable this unless you know what you're doing.
|
||||
#
|
||||
# - Only works with offline mode
|
||||
# - Only works with server packet compression disabled
|
||||
# - Only works with vanilla Minecraft clients, does not work with modded
|
||||
# - Only tested with Minecraft 1.17.1
|
||||
|
||||
# Maxiumum time in seconds in the lobby while the server starts.
|
||||
#timeout = 600
|
||||
|
||||
# Message banner in lobby shown to client.
|
||||
#message = "§2Server is starting\n§7⌛ Please wait..."
|
||||
|
||||
# Sound effect to play when server is ready.
|
||||
#ready_sound = "block.note_block.chime"
|
||||
|
||||
[lockout]
|
||||
# Enable to prevent everybody from connecting through lazymc. Instantly kicks player.
|
||||
#enabled = false
|
||||
@ -123,5 +150,5 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
|
||||
|
||||
[config]
|
||||
# lazymc version this configuration is for.
|
||||
# Do not change unless you know what you're doing.
|
||||
# Don't change unless you know what you're doing.
|
||||
version = "0.2.0"
|
||||
|
@ -238,6 +238,9 @@ pub enum Method {
|
||||
|
||||
/// Forward connection to another host.
|
||||
Forward,
|
||||
|
||||
/// Keep client in temporary fake lobby until server is ready.
|
||||
Lobby,
|
||||
}
|
||||
|
||||
/// Join configuration.
|
||||
@ -258,6 +261,10 @@ pub struct Join {
|
||||
/// Join forward configuration.
|
||||
#[serde(default)]
|
||||
pub forward: JoinForward,
|
||||
|
||||
/// Join lobby configuration.
|
||||
#[serde(default)]
|
||||
pub lobby: JoinLobby,
|
||||
}
|
||||
|
||||
impl Default for Join {
|
||||
@ -267,6 +274,7 @@ impl Default for Join {
|
||||
kick: Default::default(),
|
||||
hold: Default::default(),
|
||||
forward: Default::default(),
|
||||
lobby: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -320,6 +328,29 @@ impl Default for JoinForward {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Join lobby configuration.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct JoinLobby {
|
||||
/// Hold client in lobby for number of seconds on connect while server starts.
|
||||
pub timeout: u32,
|
||||
|
||||
/// Message banner in lobby shown to client.
|
||||
pub message: String,
|
||||
|
||||
/// Sound effect to play when server is ready.
|
||||
pub ready_sound: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for JoinLobby {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
timeout: 10 * 60,
|
||||
message: "§2Server is starting\n§7⌛ Please wait...".into(),
|
||||
ready_sound: Some("block.note_block.chime".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lockout configuration.
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
60
src/lobby.rs
60
src/lobby.rs
@ -30,11 +30,7 @@ use crate::proxy;
|
||||
use crate::server::{Server, State};
|
||||
|
||||
// TODO: remove this before releasing feature
|
||||
pub const USE_LOBBY: bool = true;
|
||||
pub const DONT_START_SERVER: bool = false;
|
||||
const STARTING_BANNER: &str = "§2Server is starting\n§7⌛ Please wait...";
|
||||
const JOIN_SOUND: bool = true;
|
||||
const JOIN_SOUND_NAME: &str = "block.note_block.chime";
|
||||
|
||||
/// Interval to send keep-alive packets at.
|
||||
const KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(10);
|
||||
@ -42,9 +38,6 @@ const KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(10);
|
||||
/// Auto incrementing ID source for keep alive packets.
|
||||
const KEEP_ALIVE_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Lobby clients may wait a maximum of 10 minutes for the server to come online.
|
||||
const SERVER_WAIT_TIMEOUT: Duration = Duration::from_secs(10 * 60);
|
||||
|
||||
/// Timeout for creating new server connection for lobby client.
|
||||
const SERVER_CONNECT_TIMEOUT: Duration = Duration::from_secs(2 * 60);
|
||||
|
||||
@ -129,18 +122,17 @@ pub async fn serve(
|
||||
send_lobby_play_packets(&mut writer, &server).await?;
|
||||
|
||||
// Wait for server to come online, then set up new connection to it
|
||||
stage_wait(server.clone(), &mut writer).await?;
|
||||
stage_wait(&server, &config, &mut writer).await?;
|
||||
let (mut outbound, mut server_buf) = connect_to_server(client_info, &config).await?;
|
||||
|
||||
// Grab join game packet from server
|
||||
let join_game = wait_for_server_join_game(&mut outbound, &mut server_buf).await?;
|
||||
|
||||
// Reset lobby title, player position and play sound effect
|
||||
// Reset lobby title
|
||||
send_lobby_title(&mut writer, "").await?;
|
||||
if JOIN_SOUND {
|
||||
send_lobby_player_pos(&mut writer).await?;
|
||||
send_lobby_sound_effect(&mut writer).await?;
|
||||
}
|
||||
|
||||
// Play ready sound if configured
|
||||
play_lobby_ready_sound(&mut writer, &config).await?;
|
||||
|
||||
// Wait a second because Notchian servers are slow
|
||||
// See: https://wiki.vg/Protocol#Login_Success
|
||||
@ -205,6 +197,23 @@ async fn respond_login_success(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Play lobby ready sound effect if configured.
|
||||
async fn play_lobby_ready_sound(writer: &mut WriteHalf<'_>, config: &Config) -> Result<(), ()> {
|
||||
if let Some(sound_name) = config.join.lobby.ready_sound.as_ref() {
|
||||
// Must not be empty string
|
||||
if sound_name.trim().is_empty() {
|
||||
warn!(target: "lazymc::lobby", "Lobby ready sound effect is an empty string, you should remove the configuration item instead");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Play sound effect
|
||||
send_lobby_player_pos(writer).await?;
|
||||
send_lobby_sound_effect(writer, sound_name).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send packets to client to get workable play state for lobby world.
|
||||
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
|
||||
@ -405,9 +414,9 @@ async fn send_lobby_title(writer: &mut WriteHalf<'_>, text: &str) -> Result<(),
|
||||
}
|
||||
|
||||
/// Send lobby ready sound effect to client.
|
||||
async fn send_lobby_sound_effect(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
async fn send_lobby_sound_effect(writer: &mut WriteHalf<'_>, sound_name: &str) -> Result<(), ()> {
|
||||
let packet = NamedSoundEffect {
|
||||
sound_name: JOIN_SOUND_NAME.into(),
|
||||
sound_name: sound_name.into(),
|
||||
sound_category: 0,
|
||||
effect_pos_x: 0,
|
||||
effect_pos_y: 0,
|
||||
@ -453,7 +462,7 @@ async fn send_respawn_from_join(writer: &mut WriteHalf<'_>, join_game: JoinGame)
|
||||
/// An infinite keep-alive loop.
|
||||
///
|
||||
/// This will keep sending keep-alive and title packets to the client until it is dropped.
|
||||
async fn keep_alive_loop(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
async fn keep_alive_loop(writer: &mut WriteHalf<'_>, config: &Config) -> Result<(), ()> {
|
||||
let mut interval = time::interval(KEEP_ALIVE_INTERVAL);
|
||||
|
||||
loop {
|
||||
@ -463,7 +472,7 @@ async fn keep_alive_loop(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
|
||||
// Send keep alive and title packets
|
||||
send_keep_alive(writer).await?;
|
||||
send_lobby_title(writer, STARTING_BANNER).await?;
|
||||
send_lobby_title(writer, &config.join.lobby.message).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -472,17 +481,21 @@ async fn keep_alive_loop(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
||||
/// In this stage we wait for the server to come online.
|
||||
///
|
||||
/// During this stage we keep sending keep-alive and title packets to the client to keep it active.
|
||||
async fn stage_wait<'a>(server: Arc<Server>, writer: &mut WriteHalf<'a>) -> Result<(), ()> {
|
||||
async fn stage_wait<'a>(
|
||||
server: &Server,
|
||||
config: &Config,
|
||||
writer: &mut WriteHalf<'a>,
|
||||
) -> Result<(), ()> {
|
||||
select! {
|
||||
a = keep_alive_loop(writer) => a,
|
||||
b = wait_for_server(server) => b,
|
||||
a = keep_alive_loop(writer, config) => a,
|
||||
b = wait_for_server(server, config) => b,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for the server to come online.
|
||||
///
|
||||
/// Returns `Ok(())` once the server is online, returns `Err(())` if waiting failed.
|
||||
async fn wait_for_server<'a>(server: Arc<Server>) -> Result<(), ()> {
|
||||
async fn wait_for_server<'a>(server: &Server, config: &Config) -> Result<(), ()> {
|
||||
debug!(target: "lazymc::lobby", "Waiting on server to come online...");
|
||||
|
||||
// A task to wait for suitable server state
|
||||
@ -514,7 +527,8 @@ async fn wait_for_server<'a>(server: Arc<Server>) -> Result<(), ()> {
|
||||
};
|
||||
|
||||
// Wait for server state with timeout
|
||||
match time::timeout(SERVER_WAIT_TIMEOUT, task_wait).await {
|
||||
let timeout = Duration::from_secs(config.join.lobby.timeout as u64);
|
||||
match time::timeout(timeout, task_wait).await {
|
||||
// Relay client to proxy
|
||||
Ok(true) => {
|
||||
debug!(target: "lazymc::lobby", "Server ready for lobby client");
|
||||
@ -526,7 +540,7 @@ async fn wait_for_server<'a>(server: Arc<Server>) -> Result<(), ()> {
|
||||
|
||||
// Timeout reached, disconnect
|
||||
Err(_) => {
|
||||
warn!(target: "lazymc::lobby", "Lobby client waiting for server to come online reached timeout of {}s", SERVER_WAIT_TIMEOUT.as_secs());
|
||||
warn!(target: "lazymc::lobby", "Lobby client waiting for server to come online reached timeout of {}s", timeout.as_secs());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,22 +140,6 @@ pub async fn serve(
|
||||
Server::start(config.clone(), server.clone(), username).await;
|
||||
}
|
||||
|
||||
// Lobby mode
|
||||
if lobby::USE_LOBBY {
|
||||
// // Hold login packet and remaining read bytes
|
||||
// hold_queue.extend(raw);
|
||||
// hold_queue.extend(buf.split_off(0));
|
||||
|
||||
// Build queue with login packet and any additionally received
|
||||
let mut queue = BytesMut::with_capacity(raw.len() + buf.len());
|
||||
queue.extend(raw);
|
||||
queue.extend(buf.split_off(0));
|
||||
|
||||
// Start lobby
|
||||
lobby::serve(client, client_info, inbound, config, server, queue).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Use join occupy methods
|
||||
for method in &config.join.methods {
|
||||
match method {
|
||||
@ -214,6 +198,22 @@ pub async fn serve(
|
||||
|
||||
// TODO: do not consume client here, allow other join method on fail
|
||||
}
|
||||
|
||||
// Lobby method, keep client in lobby while server starts
|
||||
Method::Lobby => {
|
||||
trace!(target: "lazymc", "Using lobby method to occupy joining client");
|
||||
|
||||
// Build queue with login packet and any additionally received
|
||||
let mut queue = BytesMut::with_capacity(raw.len() + buf.len());
|
||||
queue.extend(raw);
|
||||
queue.extend(buf.split_off(0));
|
||||
|
||||
// Start lobby
|
||||
lobby::serve(client, client_info, inbound, config, server, queue).await?;
|
||||
return Ok(());
|
||||
|
||||
// TODO: do not consume client here, allow other join method on fail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user