mirror of
https://github.com/timvisee/lazymc.git
synced 2025-05-19 12:50:23 -07:00
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.
|
# Forwarded-to server will receive original client handshake and login request as received by lazymc.
|
||||||
#address = "127.0.0.1:25565"
|
#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]
|
[lockout]
|
||||||
# Enable to prevent everybody from connecting through lazymc. Instantly kicks player.
|
# Enable to prevent everybody from connecting through lazymc. Instantly kicks player.
|
||||||
#enabled = false
|
#enabled = false
|
||||||
@ -123,5 +150,5 @@ command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
|
|||||||
|
|
||||||
[config]
|
[config]
|
||||||
# lazymc version this configuration is for.
|
# 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"
|
version = "0.2.0"
|
||||||
|
@ -238,6 +238,9 @@ pub enum Method {
|
|||||||
|
|
||||||
/// Forward connection to another host.
|
/// Forward connection to another host.
|
||||||
Forward,
|
Forward,
|
||||||
|
|
||||||
|
/// Keep client in temporary fake lobby until server is ready.
|
||||||
|
Lobby,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Join configuration.
|
/// Join configuration.
|
||||||
@ -258,6 +261,10 @@ pub struct Join {
|
|||||||
/// Join forward configuration.
|
/// Join forward configuration.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub forward: JoinForward,
|
pub forward: JoinForward,
|
||||||
|
|
||||||
|
/// Join lobby configuration.
|
||||||
|
#[serde(default)]
|
||||||
|
pub lobby: JoinLobby,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Join {
|
impl Default for Join {
|
||||||
@ -267,6 +274,7 @@ impl Default for Join {
|
|||||||
kick: Default::default(),
|
kick: Default::default(),
|
||||||
hold: Default::default(),
|
hold: Default::default(),
|
||||||
forward: 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.
|
/// Lockout configuration.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
60
src/lobby.rs
60
src/lobby.rs
@ -30,11 +30,7 @@ use crate::proxy;
|
|||||||
use crate::server::{Server, State};
|
use crate::server::{Server, State};
|
||||||
|
|
||||||
// TODO: remove this before releasing feature
|
// TODO: remove this before releasing feature
|
||||||
pub const USE_LOBBY: bool = true;
|
|
||||||
pub const DONT_START_SERVER: bool = false;
|
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.
|
/// Interval to send keep-alive packets at.
|
||||||
const KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(10);
|
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.
|
/// Auto incrementing ID source for keep alive packets.
|
||||||
const KEEP_ALIVE_ID: AtomicU64 = AtomicU64::new(0);
|
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.
|
/// Timeout for creating new server connection for lobby client.
|
||||||
const SERVER_CONNECT_TIMEOUT: Duration = Duration::from_secs(2 * 60);
|
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?;
|
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, &config, &mut writer).await?;
|
||||||
let (mut outbound, mut server_buf) = connect_to_server(client_info, &config).await?;
|
let (mut outbound, mut server_buf) = connect_to_server(client_info, &config).await?;
|
||||||
|
|
||||||
// Grab join game packet from server
|
// Grab join game packet from server
|
||||||
let join_game = wait_for_server_join_game(&mut outbound, &mut server_buf).await?;
|
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?;
|
send_lobby_title(&mut writer, "").await?;
|
||||||
if JOIN_SOUND {
|
|
||||||
send_lobby_player_pos(&mut writer).await?;
|
// Play ready sound if configured
|
||||||
send_lobby_sound_effect(&mut writer).await?;
|
play_lobby_ready_sound(&mut writer, &config).await?;
|
||||||
}
|
|
||||||
|
|
||||||
// Wait a second because Notchian servers are slow
|
// Wait a second because Notchian servers are slow
|
||||||
// See: https://wiki.vg/Protocol#Login_Success
|
// See: https://wiki.vg/Protocol#Login_Success
|
||||||
@ -205,6 +197,23 @@ async fn respond_login_success(
|
|||||||
Ok(())
|
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.
|
/// Send packets to client to get workable play state for lobby world.
|
||||||
async fn send_lobby_play_packets(writer: &mut WriteHalf<'_>, server: &Server) -> 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
|
||||||
@ -405,9 +414,9 @@ async fn send_lobby_title(writer: &mut WriteHalf<'_>, text: &str) -> Result<(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send lobby ready sound effect to client.
|
/// 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 {
|
let packet = NamedSoundEffect {
|
||||||
sound_name: JOIN_SOUND_NAME.into(),
|
sound_name: sound_name.into(),
|
||||||
sound_category: 0,
|
sound_category: 0,
|
||||||
effect_pos_x: 0,
|
effect_pos_x: 0,
|
||||||
effect_pos_y: 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.
|
/// An infinite keep-alive loop.
|
||||||
///
|
///
|
||||||
/// This will keep sending keep-alive and title packets to the client until it is dropped.
|
/// 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);
|
let mut interval = time::interval(KEEP_ALIVE_INTERVAL);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -463,7 +472,7 @@ async fn keep_alive_loop(writer: &mut WriteHalf<'_>) -> Result<(), ()> {
|
|||||||
|
|
||||||
// Send keep alive and title packets
|
// Send keep alive and title packets
|
||||||
send_keep_alive(writer).await?;
|
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.
|
/// 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.
|
/// 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! {
|
select! {
|
||||||
a = keep_alive_loop(writer) => a,
|
a = keep_alive_loop(writer, config) => a,
|
||||||
b = wait_for_server(server) => b,
|
b = wait_for_server(server, config) => b,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
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...");
|
debug!(target: "lazymc::lobby", "Waiting on server to come online...");
|
||||||
|
|
||||||
// A task to wait for suitable server state
|
// 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
|
// 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
|
// Relay client to proxy
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
debug!(target: "lazymc::lobby", "Server ready for lobby client");
|
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
|
// Timeout reached, disconnect
|
||||||
Err(_) => {
|
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;
|
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
|
// Use join occupy methods
|
||||||
for method in &config.join.methods {
|
for method in &config.join.methods {
|
||||||
match method {
|
match method {
|
||||||
@ -214,6 +198,22 @@ pub async fn serve(
|
|||||||
|
|
||||||
// TODO: do not consume client here, allow other join method on fail
|
// 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