From 81dc4ec3d9208af06b25c9649e43cd714dfc376e Mon Sep 17 00:00:00 2001 From: G2-Games Date: Fri, 11 Oct 2024 11:17:27 -0500 Subject: [PATCH] Worked on state. Added preliminary item and block lists. --- src/blocks_items.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++ src/chunk.rs | 11 ++- src/main.rs | 157 +++++++++++++++++++++++++----------- src/player.rs | 2 +- src/position.rs | 12 +-- src/state.rs | 81 +++++++++++++++++-- 6 files changed, 386 insertions(+), 65 deletions(-) create mode 100644 src/blocks_items.rs diff --git a/src/blocks_items.rs b/src/blocks_items.rs new file mode 100644 index 0000000..718b8e5 --- /dev/null +++ b/src/blocks_items.rs @@ -0,0 +1,188 @@ +#[repr(i16)] +pub enum Block { + Unknown = -1, + Stone = 1, + Grass = 2, + Dirt = 3, + Cobblestone = 4, + Planks = 5, + Sapling = 6, + Bedrock = 7, + WaterStill = 8, + WaterMoving = 9, + LavaStill = 10, + LavaMoving = 11, + Sand = 12, + Gravel = 13, + OreGold = 14, + OreIron = 15, + OreCoal = 16, + Wood = 17, + Leaves = 18, + Sponge = 19, + Glass = 20, + Cloth = 35, + PlantYellow = 37, + PlantRed = 38, + MushroomBrown = 39, + MushroomRed = 40, + BlockGold = 41, + BlockSteel = 42, + StairDouble = 43, + StairSingle = 44, + Brick = 45, + Tnt = 46, + BookShelf = 47, + CobblestoneMossy = 48, + Obsidian = 49, + TorchWood = 50, + Fire = 51, + MobSpawner = 52, + StairCompactPlanks = 53, + Crate = 54, + RedstoneWire = 55, + OreDiamond = 56, + BlockDiamond = 57, + Workbench = 58, + Crops = 59, + TilledField = 60, + StoneOvenIdle = 61, + StoneOvenActive = 62, + SignPost = 63, + DoorWood = 64, + Ladder = 65, + MinecartTrack = 66, + StairCompactCobblestone = 67, + SignWall = 68, + Lever = 69, + PressurePlateStone = 70, + DoorSteel = 71, + PressurePlatePlanks = 72, + OreRedstone = 73, + OreRedstoneGlowing = 74, + TorchRedstoneIdle = 75, + TorchRedstoneActive = 76, + Button = 77, + Snow = 78, + BlockIce = 79, + BlockSnow = 80, + Cactus = 81, + BlockClay = 82, + Reed = 83, + Jukebox = 84, + Fence = 85, + Pumpkin = 86, + BloodStone = 87, + SlowSand = 88, + LightStone = 89, + Portal = 90, + PumpkinLantern = 91, +} + +#[repr(i16)] +pub enum Item { + Unknown = -1, + ShovelSteel = 256, + PickaxeSteel = 257, + AxeSteel = 258, + FlintAndSteel = 259, + AppleRed = 260, + Bow = 261, + Arrow = 262, + Coal = 263, + Diamond = 264, + IngotIron = 265, + IngotGold = 266, + SwordSteel = 267, + SwordWood = 268, + ShovelWood = 269, + PickaxeWood = 270, + AxeWood = 271, + SwordStone = 272, + ShovelStone = 273, + PickaxeStone = 274, + AxeStone = 275, + SwordDiamond = 276, + ShovelDiamond = 277, + PickaxeDiamond = 278, + AxeDiamond = 279, + Stick = 280, + BowlEmpty = 281, + BowlSoup = 282, + SwordGold = 283, + ShovelGold = 284, + PickaxeGold = 285, + AxeGold = 286, + Silk = 287, + Feather = 288, + Gunpowder = 289, + HoeWood = 290, + HoeStone = 291, + HoeSteel = 292, + HoeDiamond = 293, + HoeGold = 294, + Seeds = 295, + Wheat = 296, + Bread = 297, + HelmetLeather = 298, + PlateLeather = 299, + LegsLeather = 300, + BootsLeather = 301, + HelmetChain = 302, + PlateChain = 303, + LegsChain = 304, + BootsChain = 305, + HelmetSteel = 306, + PlateSteel = 307, + LegsSteel = 308, + BootsSteel = 309, + HelmetDiamond = 310, + PlateDiamond = 311, + LegsDiamond = 312, + BootsDiamond = 313, + HelmetGold = 314, + PlateGold = 315, + LegsGold = 316, + BootsGold = 317, + Flint = 318, + PorkRaw = 319, + PorkCooked = 320, + Painting = 321, + AppleGold = 322, + Sign = 323, + DoorWood = 324, + BucketEmpty = 325, + BucketWater = 326, + BucketLava = 327, + MinecartEmpty = 328, + Saddle = 329, + DoorSteel = 330, + Redstone = 331, + Snowball = 332, + Boat = 333, + Leather = 334, + BucketMilk = 335, + Brick = 336, + Clay = 337, + Reed = 338, + Paper = 339, + Book = 340, + SlimeBall = 341, + MinecartCrate = 342, + MinecartPowered = 343, + Egg = 344, + Compass = 345, + FishingRod = 346, + PocketSundial = 347, + LightStoneDust = 348, + FishRaw = 349, + FishCooked = 350, + Record13 = 2000, + RecordCat = 2001, +} + +impl Item { + fn index() -> i16 { + 256 + } +} diff --git a/src/chunk.rs b/src/chunk.rs index b24543e..a97ece6 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -47,13 +47,12 @@ const CHUNK_TOTAL_BLOCKS: usize = CHUNK_WIDTH_X * CHUNK_WIDTH_Z * CHUNK_HEIGHT_Y impl BlockArray { fn compress(self) -> Vec { let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); - 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(); + encoder.write_all(&self.blocks).unwrap(); + encoder.write_all(&self.metadata).unwrap(); + encoder.write_all(&self.block_light).unwrap(); + encoder.write_all(&self.sky_light).unwrap(); - output_buf + encoder.finish().unwrap() } pub fn new_air() -> Self { diff --git a/src/main.rs b/src/main.rs index 7b24223..0535704 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,31 +4,33 @@ mod chunk; mod position; mod state; mod player; +mod blocks_items; -use std::{io::{self, Write}, net::{TcpListener, TcpStream}, sync::RwLock}; +use std::{io::{self, Write}, net::{TcpListener, TcpStream}, sync::{atomic::{self, AtomicI32}, Arc, RwLock}, thread}; use base16ct::lower::encode_string; use chunk::{BlockArray, MapChunk, PreChunk}; -use log::{info, warn}; +use log::{error, info, warn}; 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 position::{PlayerLook, PlayerPosition, PlayerPositionLook}; +use state::{GameState, PlayerState}; use utils::{MCString, ReadMCString, WriteMCString}; use rand::random; -/// List of players. -const PLAYER_LIST: RwLock> = RwLock::new(Vec::new()); - /// The current Entity ID. Incremented by one every time there is a new entity. -const ENTITY_ID: RwLock = RwLock::new(0); +/// +/// This value should rarely be accessed directly, and definitely never updated. +static ENTITY_ID: AtomicI32 = AtomicI32::new(0); +/// Get an Entity ID and increment the global value by 1. +#[inline] fn get_eid() -> i32 { - let eid = ENTITY_ID.read().unwrap().clone(); - *ENTITY_ID.write().unwrap() += 1; + let eid = ENTITY_ID.load(atomic::Ordering::Relaxed); + ENTITY_ID.store(eid + 1, atomic::Ordering::Relaxed); eid } @@ -38,31 +40,76 @@ fn main() { .filter_level(log::LevelFilter::Debug) .init(); + info!("Setting up game state"); + let game_state: Arc> = Arc::new(RwLock::new(GameState::new())); + 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!("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(); - } - warn!("Lost connection to client"); + let mut game_state = Arc::clone(&game_state); + thread::spawn(move || { + player_loop( + &mut connection, + &mut game_state, + ).unwrap(); + + info!("Connection dropped for {}", connection.peer_addr().unwrap()); + }); } } -fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<(), io::Error> { +fn player_loop( + mut connection: &mut TcpStream, + game_state: &mut Arc>, +) -> Result<(), io::Error> { + let mut player_state = PlayerState::new_invalid(); + loop { + if let Ok(cmd) = connection.read_u8() { + let command = Command::from_u8(cmd); + if command.is_none() { + error!("COMMAND: {command:?} (0x{cmd:02X?})"); + panic!("This command isn't implemented yet"); + } + + handle_command( + &mut connection, + command.unwrap(), + &mut player_state, + ).unwrap(); + } else { + break; + } + + if player_state.is_valid() { + if game_state.read().unwrap().player_list().get(player_state.username()).is_some_and(|p| *p != player_state) + || game_state.read().unwrap().player_list().get(player_state.username()).is_none() + { + game_state.write() + .unwrap() + .player_list_mut() + .insert(player_state.username().clone(), player_state.clone()); + } + } + + } + + Ok(()) +} + +fn handle_command( + mut connection: &mut TcpStream, + command: Command, + player_state: &mut PlayerState, +) -> Result<(), io::Error> { match command { Command::Handshake => { let username = connection.read_mcstring()?; let random_number = random::(); let random_hash = encode_string(md5::compute(random_number.to_le_bytes()).as_slice()); - connection.write_u8(0x02)?; + connection.write_u8(Command::Handshake as u8)?; connection.write_mcstring(&MCString::try_from(random_hash).unwrap())?; info!("Handshake with {username} successful"); @@ -75,20 +122,14 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<() let _map_seed = connection.read_i64::()?; let _dimension = connection.read_i8()?; + // Return a successful login packet to the client let eid = get_eid(); - let login_packet = ServerLoginPacket { - entity_id: eid, - unknown1: MCString::default(), - unknown2: MCString::default(), - map_seed: 0, - dimension: 0, - }; + let login_packet = ServerLoginPacket::new(eid, 0, 0); connection.write_u8(Command::Login as u8).unwrap(); - connection.write(&login_packet.to_bytes())?; - - PLAYER_LIST.write().unwrap().push(PlayerState::new(username.to_string(), eid)); + connection.write_all(&login_packet.to_bytes())?; info!("{username} logged in. Protocol version {protocol_version}"); + *player_state = PlayerState::new(username.to_string(), eid); for i in -10..10 { for o in -10..10 { @@ -105,15 +146,15 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<() connection.write_u8(Command::SpawnPosition as u8)?; connection.write_u32::(0)?; - connection.write_u32::(70)?; + connection.write_u32::(0)?; connection.write_u32::(0)?; - let playerpos = PlayerPositionAndLook { + let playerpos = PlayerPositionLook { position: PlayerPosition { - position_x: 1.0, + position_x: 0.5, stance: 0.0, position_y: 9.63, - position_z: 1.0, + position_z: 0.5, }, look: PlayerLook { yaw: 0.0, @@ -124,19 +165,28 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<() connection.write_all(&playerpos.to_bytes())?; }, Command::ChatMessage => { - info!("Chat Message Recieved: {}", connection.read_mcstring().unwrap()); + let message = connection.read_mcstring().unwrap(); + info!("Chat Message Recieved: {message}"); } Command::Player => { connection.read_u8()?; }, Command::PlayerLook => { - let _look = PlayerLook::from_bytes(&mut connection); + let look = PlayerLook::from_bytes(&mut connection); + player_state.set_look(look); } Command::PlayerPosition => { - let _pos = PlayerPosition::from_bytes(&mut connection); + let pos = PlayerPosition::from_bytes(&mut connection); + player_state.set_position(pos); } Command::PlayerPositionAndLook => { - let _poslook = PlayerPositionAndLook::from_bytes(&mut connection); + let poslook = PlayerPositionLook::from_bytes(&mut connection); + player_state.set_look(poslook.look); + player_state.set_position(poslook.position); + } + Command::HoldingChange => { + let _unused = connection.read_i32::()?; + let block_id = connection.read_i16::()?; } Command::PlayerDigging => { let _status = DiggingStatus::from_u8(connection.read_u8()?).unwrap(); @@ -147,12 +197,10 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<() } Command::PlayerBlockPlacement => { let _status = PlayerBlockPlacement::from_bytes(&mut connection); - dbg!(_status); } - Command::ArmAnimation => { + Command::Animation => { let _eid = connection.read_i32::()?; - let _animate = connection.read_u8()? != 0; - dbg!(_animate); + let _animate = connection.read_u8()?; } Command::Disconnect => { let disconnect_string = connection.read_mcstring()?; @@ -160,12 +208,12 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<() connection.shutdown(std::net::Shutdown::Both)?; } Command::KeepAlive => { - let _ = connection.write_u8(0x00); + connection.write_u8(Command::KeepAlive as u8)?; } Command::UpdateHealth => { connection.read_u8()?; } - c => unimplemented!("This command ({c:?}) is probably `Server -> Client` only") + c => unimplemented!("This command ({c:?}) is probably `Server -> Client` only; thus it is unimplemented for the other way around!") } Ok(()) @@ -190,9 +238,26 @@ enum Command { PlayerPositionAndLook = 0x0D, PlayerDigging = 0x0E, PlayerBlockPlacement = 0x0F, - ArmAnimation = 0x12, + HoldingChange = 0x10, + AddToInventory = 0x11, + Animation = 0x12, + NamedEntitySpawn = 0x14, + PickupSpawn = 0x15, + CollectItem = 0x16, + AddObject = 0x17, + MobSpawn = 0x18, + EntityVelocity = 0x1C, + DestroyEntity = 0x1D, + Entity = 0x1E, + EntityRelativeMove = 0x1F, + EntityLook = 0x20, + EntityLookAndRelativeMove = 0x21, + EntityTeleport = 0x22, + AttachEntity = 0x27, PreChunk = 0x32, MapChunk = 0x33, + BlockChange = 0x35, + ComplexEntities = 0x3B, Disconnect = 0xFF, } diff --git a/src/player.rs b/src/player.rs index 1d99f00..5c11d8d 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,6 +1,6 @@ use std::io::Read; -use byteorder::{ReadBytesExt, WriteBytesExt, BE}; +use byteorder::{ReadBytesExt, BE}; use num_derive::FromPrimitive; use num_traits::FromPrimitive; diff --git a/src/position.rs b/src/position.rs index 48a9b1e..3df292f 100644 --- a/src/position.rs +++ b/src/position.rs @@ -4,13 +4,13 @@ use byteorder::{ReadBytesExt, WriteBytesExt, BE}; use crate::byte_ops::ToBytes; -#[derive(Debug, Clone, Copy)] -pub struct PlayerPositionAndLook { +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct PlayerPositionLook { pub position: PlayerPosition, pub look: PlayerLook, } -impl ToBytes for PlayerPositionAndLook { +impl ToBytes for PlayerPositionLook { type Bytes = Vec; fn to_bytes(self) -> Self::Bytes { @@ -27,7 +27,7 @@ impl ToBytes for PlayerPositionAndLook { } } -impl PlayerPositionAndLook { +impl PlayerPositionLook { pub fn from_bytes(stream: &mut R) -> Self { let position_x = stream.read_f64::().unwrap(); let position_y = stream.read_f64::().unwrap(); @@ -54,7 +54,7 @@ impl PlayerPositionAndLook { } } -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct PlayerPosition { pub position_x: f64, pub position_y: f64, @@ -95,7 +95,7 @@ impl PlayerPosition { } } -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy, PartialEq)] pub struct PlayerLook { pub yaw: f32, pub pitch: f32, diff --git a/src/state.rs b/src/state.rs index 6e5ec9c..389c661 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,16 +1,36 @@ +use std::collections::BTreeMap; + use crate::{ - chunk::MapChunk, position::{PlayerLook, PlayerPosition}, + chunk::{BlockType, MapChunk}, + position::{PlayerLook, PlayerPosition, PlayerPositionLook}, }; pub struct GameState { - + player_list: BTreeMap, } +impl GameState { + pub fn new() -> Self { + Self { + player_list: BTreeMap::new() + } + } + + pub fn player_list(&self) -> &BTreeMap { + &self.player_list + } + + pub fn player_list_mut(&mut self) -> &mut BTreeMap { + &mut self.player_list + } +} + +#[derive(Debug, Clone, PartialEq)] pub struct PlayerState { eid: i32, username: String, - position: PlayerPosition, - look: PlayerLook, + holding: i16, + position_look: PlayerPositionLook, } impl PlayerState { @@ -19,10 +39,59 @@ impl PlayerState { Self { eid, username, - position: PlayerPosition::default(), - look: PlayerLook::default(), + holding: -1, + position_look: PlayerPositionLook::default(), } } + + pub fn new_invalid() -> Self { + Self { + eid: -1, + username: String::new(), + holding: -1, + position_look: PlayerPositionLook::default(), + } + } + + pub fn is_valid(&self) -> bool { + if self.eid >= 0 && self.username != String::new() { + true + } else { + false + } + } + + pub fn username(&self) -> &String { + &self.username + } + + pub fn look(&self) -> &PlayerLook { + &self.position_look.look + } + + pub fn position(&self) -> &PlayerPosition { + &self.position_look.position + } + + pub fn position_look(&self) -> &PlayerPositionLook { + &self.position_look + } + + pub fn holding(&self) -> &BlockType { + &self.holding + } + + pub fn set_position(&mut self, position: PlayerPosition) { + self.position_look.position = position + } + + pub fn set_look(&mut self, look: PlayerLook) { + self.position_look.look = look + } + + pub fn set_holding(&mut self, holding: i16) { + self.holding = holding + } } pub struct WorldState {