diff --git a/src/chunk.rs b/src/chunk.rs index 50dea0e..b24543e 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,6 +1,7 @@ use byteorder::{WriteBytesExt, BE}; use flate2::write::ZlibEncoder; use flate2::Compression; +use num_derive::FromPrimitive; use std::io::prelude::*; use crate::byte_ops::ToBytes; @@ -32,79 +33,71 @@ impl MapChunk { #[derive(Debug, Clone)] pub struct BlockArray { - compressed_size: i32, - compressed_data: Vec, + blocks: Vec, + metadata: Vec, + block_light: Vec, + sky_light: Vec, } +const CHUNK_WIDTH_X: usize = 16; +const CHUNK_WIDTH_Z: usize = 16; +const CHUNK_HEIGHT_Y: usize = 128; +const CHUNK_TOTAL_BLOCKS: usize = CHUNK_WIDTH_X * CHUNK_WIDTH_Z * CHUNK_HEIGHT_Y; + impl BlockArray { - pub fn new_air() -> Self { - let mut block_vec = Vec::new(); - - for _ in 0..(16 * 127 * 15) { - block_vec.push(0); - } - for _ in 0..(16 * 127 * 15) / 2 { - block_vec.push(0); - } - for _ in 0..(16 * 127 * 15) / 2 { - block_vec.push(0); - } - for _ in 0..(16 * 127 * 15) / 2 { - block_vec.push(0); - } - + fn compress(self) -> Vec { let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); - encoder.write(&block_vec).unwrap(); + encoder.write(&self.blocks).unwrap(); + encoder.write(&self.metadata).unwrap(); + encoder.write(&self.block_light).unwrap(); + encoder.write(&self.sky_light).unwrap(); let output_buf = encoder.finish().unwrap(); + output_buf + } + + pub fn new_air() -> Self { Self { - compressed_size: output_buf.len() as i32, - compressed_data: output_buf, + blocks: vec![0; CHUNK_TOTAL_BLOCKS], + metadata: vec![0; CHUNK_TOTAL_BLOCKS / 2], + block_light: vec![0; CHUNK_TOTAL_BLOCKS / 2], + sky_light: vec![0xFF; CHUNK_TOTAL_BLOCKS / 2], } } pub fn new_superflat() -> Self { - let mut block_vec = vec![0; 16 * 16 * 128]; - - for x in 0..16 { - for y in 0..128 { - for z in 0..16 { - let pos = y + (z * (128)) + (x * (128) * (16)); + let mut blocks = vec![0; CHUNK_TOTAL_BLOCKS]; + for y in 0..CHUNK_HEIGHT_Y { + for x in 0..CHUNK_WIDTH_X { + for z in 0..CHUNK_WIDTH_Z { + let pos = y + (z * (CHUNK_HEIGHT_Y)) + (x * (CHUNK_HEIGHT_Y) * (CHUNK_WIDTH_X)); if y == 7 { - block_vec[pos] = BlockType::Grass as u8; + blocks[pos] = BlockType::Grass as u8; } else if y > 0 && y < 7 { - block_vec[pos] = BlockType::Dirt as u8; + blocks[pos] = BlockType::Dirt as u8; } else if y == 0 { - block_vec[pos] = BlockType::Bedrock as u8; + blocks[pos] = BlockType::Bedrock as u8; } else { - block_vec[pos] = 0; + blocks[pos] = BlockType::Air as u8; } } } } - for _ in 0..(16 * 128 * 16) / 2 { - block_vec.push(0); - } - for _ in 0..(16 * 128 * 16) / 2 { - block_vec.push(0); - } - for _ in 0..(16 * 128 * 16) / 2 { - block_vec.push(0xFF); - } - - let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); - encoder.write(&block_vec).unwrap(); - let output_buf = encoder.finish().unwrap(); Self { - compressed_size: output_buf.len() as i32, - compressed_data: output_buf, + blocks, + metadata: vec![0xFF; CHUNK_TOTAL_BLOCKS / 2], + block_light: vec![0; CHUNK_TOTAL_BLOCKS / 2], + sky_light: vec![0xFF; CHUNK_TOTAL_BLOCKS / 2], } } } -#[repr(u8)] -enum BlockType { +#[repr(i16)] +#[derive(Debug, Clone, Copy)] +#[derive(FromPrimitive)] +pub enum BlockType { + None = -1, Air, Stone, Grass, @@ -127,8 +120,9 @@ impl ToBytes for MapChunk { buffer.write_u8(self.size_y).unwrap(); buffer.write_u8(self.size_z).unwrap(); - buffer.write_i32::(self.compressed_data.compressed_size).unwrap(); - buffer.write_all(&self.compressed_data.compressed_data).unwrap(); + let block_buf = self.compressed_data.compress(); + buffer.write_i32::(block_buf.len() as i32).unwrap(); + buffer.write_all(&block_buf).unwrap(); buffer } diff --git a/src/main.rs b/src/main.rs index 08d04bf..7b24223 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,10 @@ mod utils; mod byte_ops; mod chunk; mod position; +mod state; +mod player; -use std::{cell::LazyCell, io::{self, Write}, net::{TcpListener, TcpStream}}; +use std::{io::{self, Write}, net::{TcpListener, TcpStream}, sync::RwLock}; use base16ct::lower::encode_string; use chunk::{BlockArray, MapChunk, PreChunk}; @@ -12,23 +14,24 @@ use byteorder::{ReadBytesExt, WriteBytesExt, BE}; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use byte_ops::ToBytes; +use player::{DiggingStatus, PlayerBlockPlacement}; use position::{PlayerLook, PlayerPosition, PlayerPositionAndLook}; +use state::PlayerState; use utils::{MCString, ReadMCString, WriteMCString}; use rand::random; -const CHUNKS: LazyCell> = LazyCell::new(|| { - let mut mapchunk = Vec::new(); - for i in -10..10 { - for o in -10..10 { - let x = i * 16; - let z = o * 16; +/// List of players. +const PLAYER_LIST: RwLock> = RwLock::new(Vec::new()); - mapchunk.push(MapChunk::new(x, z, BlockArray::new_superflat())); - } - } +/// The current Entity ID. Incremented by one every time there is a new entity. +const ENTITY_ID: RwLock = RwLock::new(0); - mapchunk -}); +fn get_eid() -> i32 { + let eid = ENTITY_ID.read().unwrap().clone(); + *ENTITY_ID.write().unwrap() += 1; + + eid +} fn main() { colog::default_builder() @@ -36,13 +39,15 @@ fn main() { .init(); let listener = TcpListener::bind("0.0.0.0:25565").unwrap(); + info!("Server started and listening on {}", listener.local_addr().unwrap()); for mut connection in listener.incoming().filter_map(|c| c.ok()) { - info!("Connected to client @ {}", connection.peer_addr().unwrap()); + info!("Player joined from {}", connection.peer_addr().unwrap()); while let Some(cmd) = connection.read_u8().ok() { let command = Command::from_u8(cmd); if command.is_none() { info!("COMMAND: {command:?} (0x{cmd:02X?})"); + panic!("This command isn't implemented yet"); } handle_command(&mut connection, command.unwrap()).unwrap(); } @@ -57,27 +62,22 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<() let random_number = random::(); let random_hash = encode_string(md5::compute(random_number.to_le_bytes()).as_slice()); - info!("Handshake with {username} successful. Providing hash: {random_hash:?}"); - connection.write_u8(0x02)?; connection.write_mcstring(&MCString::try_from(random_hash).unwrap())?; + + info!("Handshake with {username} successful"); }, Command::Login => { - info!("Initiating login"); let protocol_version = connection.read_u32::()?; let username = connection.read_mcstring()?; - let password = connection.read_mcstring()?; - let map_seed = connection.read_i64::()?; - let dimension = connection.read_i8()?; - - info!("Protocol Version: {protocol_version}"); - info!("Username: {username}"); - info!("Password: {password}"); - info!("Map Seed: {map_seed}"); - info!("Dimension: {dimension}"); + // These are mostly useless + let _password = connection.read_mcstring()?; + let _map_seed = connection.read_i64::()?; + let _dimension = connection.read_i8()?; + let eid = get_eid(); let login_packet = ServerLoginPacket { - entity_id: 1, + entity_id: eid, unknown1: MCString::default(), unknown2: MCString::default(), map_seed: 0, @@ -86,7 +86,9 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<() connection.write_u8(Command::Login as u8).unwrap(); connection.write(&login_packet.to_bytes())?; - info!("Responded to auth request"); + PLAYER_LIST.write().unwrap().push(PlayerState::new(username.to_string(), eid)); + + info!("{username} logged in. Protocol version {protocol_version}"); for i in -10..10 { for o in -10..10 { @@ -137,24 +139,28 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<() let _poslook = PlayerPositionAndLook::from_bytes(&mut connection); } Command::PlayerDigging => { - let status = DiggingStatus::from_u8(connection.read_u8()?).unwrap(); - let pos_x = connection.read_i32::()?; - let pos_y = connection.read_u8()?; - let pos_z = connection.read_i32::()?; - let face = connection.read_u8()?; + let _status = DiggingStatus::from_u8(connection.read_u8()?).unwrap(); + let _pos_x = connection.read_i32::()?; + let _pos_y = connection.read_u8()?; + let _pos_z = connection.read_i32::()?; + let _face = connection.read_u8()?; + } + Command::PlayerBlockPlacement => { + let _status = PlayerBlockPlacement::from_bytes(&mut connection); + dbg!(_status); } Command::ArmAnimation => { - let eid = connection.read_i32::()?; - let animate = connection.read_u8()? != 0; + let _eid = connection.read_i32::()?; + let _animate = connection.read_u8()? != 0; + dbg!(_animate); } Command::Disconnect => { let disconnect_string = connection.read_mcstring()?; - info!("Disconnecting client: {disconnect_string}"); + info!("Disconnecting client. Reason: {disconnect_string}"); connection.shutdown(std::net::Shutdown::Both)?; } Command::KeepAlive => { let _ = connection.write_u8(0x00); - // TODO: Feed keepalive watchdog for client } Command::UpdateHealth => { connection.read_u8()?; @@ -165,16 +171,6 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<() Ok(()) } -#[repr(u8)] -#[derive(Debug, Clone, Copy)] -#[derive(FromPrimitive)] -enum DiggingStatus { - StartedDigging = 0, - Digging = 1, - StoppedDigging = 2, - BlockBroken = 3, -} - #[repr(u8)] #[derive(Debug, Clone, Copy)] #[derive(FromPrimitive)] @@ -193,6 +189,7 @@ enum Command { PlayerLook = 0x0C, PlayerPositionAndLook = 0x0D, PlayerDigging = 0x0E, + PlayerBlockPlacement = 0x0F, ArmAnimation = 0x12, PreChunk = 0x32, MapChunk = 0x33, @@ -207,6 +204,18 @@ struct ServerLoginPacket { dimension: i8, } +impl ServerLoginPacket { + pub fn new(entity_id: i32, map_seed: i64, dimension: i8) -> Self { + Self { + entity_id, + unknown1: MCString::default(), + unknown2: MCString::default(), + map_seed, + dimension, + } + } +} + impl ToBytes for ServerLoginPacket { type Bytes = Vec; diff --git a/src/player.rs b/src/player.rs new file mode 100644 index 0000000..1d99f00 --- /dev/null +++ b/src/player.rs @@ -0,0 +1,68 @@ +use std::io::Read; + +use byteorder::{ReadBytesExt, WriteBytesExt, BE}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + +use crate::chunk::BlockType; + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +#[derive(FromPrimitive)] +pub enum DiggingStatus { + StartedDigging = 0, + Digging = 1, + StoppedDigging = 2, + BlockBroken = 3, +} + +/// The face of a block, a direction. +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +#[derive(FromPrimitive)] +pub enum Direction { + NegY = 0, + PosY = 1, + NegZ = 2, + PosZ = 3, + NegX = 4, + PosX = 5, +} + +#[derive(Debug, Clone, Copy)] +pub struct PlayerDigging { + status: DiggingStatus, + position_x: i32, + position_y: u8, + position_z: i32, + face: Direction, +} + +#[derive(Debug, Clone, Copy)] +pub struct PlayerBlockPlacement { + block_id: BlockType, + position_x: i32, + position_y: u8, + position_z: i32, + direction: Direction, + //amount: u8, + //health: u8, +} + +impl PlayerBlockPlacement { + pub fn from_bytes(stream: &mut R) -> Self { + let block_id = BlockType::from_i16(stream.read_i16::().unwrap()).unwrap(); + let position_x = stream.read_i32::().unwrap(); + let position_y = stream.read_u8().unwrap(); + let position_z = stream.read_i32::().unwrap(); + let direction = Direction::from_u8(stream.read_u8().unwrap()).unwrap(); + + Self { + block_id, + position_x, + position_y, + position_z, + direction, + } + } +} diff --git a/src/position.rs b/src/position.rs index 7a6299e..48a9b1e 100644 --- a/src/position.rs +++ b/src/position.rs @@ -54,7 +54,7 @@ impl PlayerPositionAndLook { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy)] pub struct PlayerPosition { pub position_x: f64, pub position_y: f64, @@ -95,7 +95,7 @@ impl PlayerPosition { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy)] pub struct PlayerLook { pub yaw: f32, pub pitch: f32, diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..6e5ec9c --- /dev/null +++ b/src/state.rs @@ -0,0 +1,30 @@ +use crate::{ + chunk::MapChunk, position::{PlayerLook, PlayerPosition}, +}; + +pub struct GameState { + +} + +pub struct PlayerState { + eid: i32, + username: String, + position: PlayerPosition, + look: PlayerLook, +} + +impl PlayerState { + /// Create a new player when they join + pub fn new(username: String, eid: i32,) -> Self { + Self { + eid, + username, + position: PlayerPosition::default(), + look: PlayerLook::default(), + } + } +} + +pub struct WorldState { + chunks: Vec, +}