diff --git a/src/args.rs b/src/args.rs index 59ac7ab..15167c2 100644 --- a/src/args.rs +++ b/src/args.rs @@ -23,22 +23,27 @@ use clap::Parser; pub struct Args { /// The directory to load the map from. Should be DIM1, or the equivalent renamed folder. pub map_dir: String, - /// The address to serve crawlspace on. Defaults to [::] (all interfaces) if not set. - #[arg(short, long)] - addr: Option, + /// The address to serve crawlspace on. + #[arg(short, long, default_value = "[::]")] + pub addr: String, /// The port to serve crawlspace on. Defaults to 25565 if not set. - #[arg(short, long)] - port: Option, -} - -impl Args { - #[inline(always)] - pub fn addr(&self) -> String { - self.addr.clone().unwrap_or("[::]".into()) - } - - #[inline(always)] - pub fn port(&self) -> u16 { - self.port.unwrap_or(25565) - } + #[arg(short, long, default_value = "25565")] + pub port: u16, + /// The x coordinate of the spawnpoint. + #[arg(short = 'x', long, default_value = "0")] + pub spawn_x: f64, + /// The y coordinate of the spawnpoint. + #[arg(short = 'y', long, default_value = "100")] + pub spawn_y: f64, + /// The z coordinate of the spawnpoint. + #[arg(short = 'z', long, default_value = "0")] + pub spawn_z: f64, + /// The border radius, centered around the spawnpoint. Defaults to 10 chunks. One + /// chunk past the border will be loaded. + #[arg(short = 'b', long, default_value = "160")] + pub border_radius: i32, + #[arg(short, long, default_value = "Limbo")] + pub motd: String, + #[arg(long, default_value = "500")] + pub max_players: usize, } diff --git a/src/main.rs b/src/main.rs index df67255..0903431 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,8 +39,6 @@ mod world; const VERSION: &str = "1.21.1"; const VERSION_NUM: i32 = 767; -const DESCRIPTION: &str = "sheldon cooper residence"; -const MAX_PLAYERS: usize = 906; const TICK_RATE: u8 = 20; type CrawlState = Arc; @@ -80,14 +78,7 @@ async fn main() -> Result<()> { let world_cache = WorldCache::from(world); info!("Done."); - let state = Arc::new(state::State::new( - VERSION, - VERSION_NUM, - DESCRIPTION, - MAX_PLAYERS, - args.addr(), - args.port(), - )); + let state = Arc::new(state::State::new(VERSION, VERSION_NUM, args)); #[cfg(feature = "lan")] net::spawn_lan_broadcast(state.clone()).await?; diff --git a/src/net/player.rs b/src/net/player.rs index 8e5584d..170dc09 100644 --- a/src/net/player.rs +++ b/src/net/player.rs @@ -37,7 +37,8 @@ use crate::{ login::*, play::{ ConfirmTeleportS, GameEvent, GameEventC, Gamemode, KeepAliveC, LoginPlayC, - PlayerInfoUpdateC, PlayerStatus, SetCenterChunkC, SynchronisePositionC, + PlayerInfoUpdateC, PlayerStatus, SetBorderCenterC, SetBorderSizeC, SetCenterChunkC, + SynchronisePositionC, }, }, PacketState, @@ -289,21 +290,33 @@ impl SharedPlayer { drop(io); - self.teleport_awaiting(0.0, 100.0, 0.0, 0.0, 0.0).await?; + let spawnpoint = state.spawnpoint; + self.teleport_awaiting(spawnpoint.0, spawnpoint.1, spawnpoint.2, 0.0, 0.0) + .await?; + + let mut io = self.0.io.lock().await; + + io.tx(&SetBorderCenterC { + x: spawnpoint.0, + z: spawnpoint.2, + }) + .await?; + + io.tx(&SetBorderSizeC(state.border_radius as f64 * 2.0)) + .await?; let player_add = PlayerInfoUpdateC { players: &[PlayerStatus::for_player(self.uuid().await).add_player("AFK", &[])], }; - let mut io = self.0.io.lock().await; io.tx(&player_add).await?; let await_chunks = GameEventC::from(GameEvent::StartWaitingForLevelChunks); io.tx(&await_chunks).await?; let set_center = SetCenterChunkC { - x: VarInt(0), - y: VarInt(0), + x: VarInt(spawnpoint.0.floor() as i32 / 16), + y: VarInt(spawnpoint.2.floor() as i32 / 16), }; io.tx(&set_center).await?; drop(io); diff --git a/src/protocol/packets/play/world.rs b/src/protocol/packets/play/world.rs index 14d8a7b..61f9a49 100644 --- a/src/protocol/packets/play/world.rs +++ b/src/protocol/packets/play/world.rs @@ -24,7 +24,10 @@ use bytes::BufMut; use fastnbt::SerOpts; use crate::{ - protocol::{datatypes::VarInt, Encode, Packet}, + protocol::{ + datatypes::{VarInt, VarLong}, + Encode, Packet, + }, world::{ self, blocks::{BlockState, Blocks}, @@ -320,3 +323,64 @@ impl ChunkDataUpdateLightC<'_> { } } } + +#[derive(Debug)] +pub struct InitializeWorldBorderC { + pub x: f64, + pub z: f64, + pub old_diameter: f64, + pub new_diameter: f64, + pub speed: i64, + pub teleport_boundary: i32, + pub warning_blocks: i32, + pub warning_time_sec: i32, +} + +impl Packet for InitializeWorldBorderC { + const ID: i32 = 0x25; +} + +impl Encode for InitializeWorldBorderC { + fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { + self.x.encode(&mut w)?; + self.z.encode(&mut w)?; + self.old_diameter.encode(&mut w)?; + self.new_diameter.encode(&mut w)?; + VarLong(self.speed).encode(&mut w)?; + VarInt(self.teleport_boundary).encode(&mut w)?; + VarInt(self.warning_blocks).encode(&mut w)?; + VarInt(self.warning_time_sec).encode(&mut w)?; + Ok(()) + } +} + +#[derive(Debug)] +pub struct SetBorderCenterC { + pub x: f64, + pub z: f64, +} + +impl Packet for SetBorderCenterC { + const ID: i32 = 0x4D; +} + +impl Encode for SetBorderCenterC { + fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> { + self.x.encode(&mut w)?; + self.z.encode(&mut w)?; + Ok(()) + } +} + +#[derive(Debug)] +pub struct SetBorderSizeC(pub f64); + +impl Packet for SetBorderSizeC { + const ID: i32 = 0x4F; +} + +impl Encode for SetBorderSizeC { + fn encode(&self, w: impl std::io::Write) -> color_eyre::eyre::Result<()> { + self.0.encode(w) + } +} diff --git a/src/state.rs b/src/state.rs index 624d802..55580b8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -23,6 +23,7 @@ use tokio::sync::{mpsc, Mutex, Semaphore}; use tokio_util::sync::CancellationToken; use crate::{ + args::Args, net::{cache::RegistryCache, player::SharedPlayer}, protocol::packets::login::registry::ALL_REGISTRIES, }; @@ -45,22 +46,18 @@ pub struct State { pub shutdown_token: CancellationToken, pub net_sema: Arc, + + pub spawnpoint: (f64, f64, f64), + pub border_radius: i32, } impl State { #[must_use] - pub fn new( - version_name: &str, - version_number: i32, - description: &str, - max_players: usize, - addr: String, - port: u16, - ) -> Self { - let max = max_players.min(Semaphore::MAX_PERMITS); - - if max < max_players { - warn!("Requested max player count {max_players} is less than max semaphore permits {max} - limited to {max}."); + pub fn new(version_name: &str, version_number: i32, args: Args) -> Self { + let max = args.max_players.min(Semaphore::MAX_PERMITS); + + if max < args.max_players { + warn!("Requested max player count {} is less than max semaphore permits {max} - limited to {max}.", args.max_players); } let (player_send, player_recv) = mpsc::channel(16); @@ -69,11 +66,11 @@ impl State { Self { max_players: max, current_players: AtomicUsize::new(0), - description: description.to_owned(), + description: args.motd, version_name: version_name.to_owned(), version_number: version_number.to_owned(), - addr, - port, + addr: args.addr, + port: args.port, registry_cache: RegistryCache::from(&*ALL_REGISTRIES), @@ -83,6 +80,9 @@ impl State { shutdown_token, net_sema: Arc::new(Semaphore::new(max)), + + spawnpoint: (args.spawn_x, args.spawn_y, args.spawn_z), + border_radius: args.border_radius, } } }