Worked on state. Added preliminary item and block lists.

This commit is contained in:
G2-Games 2024-10-11 11:17:27 -05:00
parent e898dfa859
commit 81dc4ec3d9
6 changed files with 386 additions and 65 deletions

188
src/blocks_items.rs Normal file
View file

@ -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
}
}

View file

@ -47,13 +47,12 @@ const CHUNK_TOTAL_BLOCKS: usize = CHUNK_WIDTH_X * CHUNK_WIDTH_Z * CHUNK_HEIGHT_Y
impl BlockArray { impl BlockArray {
fn compress(self) -> Vec<u8> { fn compress(self) -> Vec<u8> {
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
encoder.write(&self.blocks).unwrap(); encoder.write_all(&self.blocks).unwrap();
encoder.write(&self.metadata).unwrap(); encoder.write_all(&self.metadata).unwrap();
encoder.write(&self.block_light).unwrap(); encoder.write_all(&self.block_light).unwrap();
encoder.write(&self.sky_light).unwrap(); encoder.write_all(&self.sky_light).unwrap();
let output_buf = encoder.finish().unwrap();
output_buf encoder.finish().unwrap()
} }
pub fn new_air() -> Self { pub fn new_air() -> Self {

View file

@ -4,31 +4,33 @@ mod chunk;
mod position; mod position;
mod state; mod state;
mod player; 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 base16ct::lower::encode_string;
use chunk::{BlockArray, MapChunk, PreChunk}; use chunk::{BlockArray, MapChunk, PreChunk};
use log::{info, warn}; use log::{error, info, warn};
use byteorder::{ReadBytesExt, WriteBytesExt, BE}; use byteorder::{ReadBytesExt, WriteBytesExt, BE};
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use byte_ops::ToBytes; use byte_ops::ToBytes;
use player::{DiggingStatus, PlayerBlockPlacement}; use player::{DiggingStatus, PlayerBlockPlacement};
use position::{PlayerLook, PlayerPosition, PlayerPositionAndLook}; use position::{PlayerLook, PlayerPosition, PlayerPositionLook};
use state::PlayerState; use state::{GameState, PlayerState};
use utils::{MCString, ReadMCString, WriteMCString}; use utils::{MCString, ReadMCString, WriteMCString};
use rand::random; use rand::random;
/// List of players.
const PLAYER_LIST: RwLock<Vec<PlayerState>> = RwLock::new(Vec::new());
/// The current Entity ID. Incremented by one every time there is a new entity. /// The current Entity ID. Incremented by one every time there is a new entity.
const ENTITY_ID: RwLock<i32> = 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 { fn get_eid() -> i32 {
let eid = ENTITY_ID.read().unwrap().clone(); let eid = ENTITY_ID.load(atomic::Ordering::Relaxed);
*ENTITY_ID.write().unwrap() += 1; ENTITY_ID.store(eid + 1, atomic::Ordering::Relaxed);
eid eid
} }
@ -38,31 +40,76 @@ fn main() {
.filter_level(log::LevelFilter::Debug) .filter_level(log::LevelFilter::Debug)
.init(); .init();
info!("Setting up game state");
let game_state: Arc<RwLock<GameState>> = Arc::new(RwLock::new(GameState::new()));
let listener = TcpListener::bind("0.0.0.0:25565").unwrap(); let listener = TcpListener::bind("0.0.0.0:25565").unwrap();
info!("Server started and listening on {}", listener.local_addr().unwrap()); info!("Server started and listening on {}", listener.local_addr().unwrap());
for mut connection in listener.incoming().filter_map(|c| c.ok()) { for mut connection in listener.incoming().filter_map(|c| c.ok()) {
info!("Player joined from {}", connection.peer_addr().unwrap()); info!("Player joined from {}", connection.peer_addr().unwrap());
while let Some(cmd) = connection.read_u8().ok() { let mut game_state = Arc::clone(&game_state);
let command = Command::from_u8(cmd); thread::spawn(move || {
if command.is_none() { player_loop(
info!("COMMAND: {command:?} (0x{cmd:02X?})"); &mut connection,
panic!("This command isn't implemented yet"); &mut game_state,
} ).unwrap();
handle_command(&mut connection, command.unwrap()).unwrap();
} info!("Connection dropped for {}", connection.peer_addr().unwrap());
warn!("Lost connection to client"); });
} }
} }
fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<(), io::Error> { fn player_loop(
mut connection: &mut TcpStream,
game_state: &mut Arc<RwLock<GameState>>,
) -> 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 { match command {
Command::Handshake => { Command::Handshake => {
let username = connection.read_mcstring()?; let username = connection.read_mcstring()?;
let random_number = random::<u128>(); let random_number = random::<u128>();
let random_hash = encode_string(md5::compute(random_number.to_le_bytes()).as_slice()); 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())?; connection.write_mcstring(&MCString::try_from(random_hash).unwrap())?;
info!("Handshake with {username} successful"); 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::<BE>()?; let _map_seed = connection.read_i64::<BE>()?;
let _dimension = connection.read_i8()?; let _dimension = connection.read_i8()?;
// Return a successful login packet to the client
let eid = get_eid(); let eid = get_eid();
let login_packet = ServerLoginPacket { let login_packet = ServerLoginPacket::new(eid, 0, 0);
entity_id: eid,
unknown1: MCString::default(),
unknown2: MCString::default(),
map_seed: 0,
dimension: 0,
};
connection.write_u8(Command::Login as u8).unwrap(); connection.write_u8(Command::Login as u8).unwrap();
connection.write(&login_packet.to_bytes())?; connection.write_all(&login_packet.to_bytes())?;
PLAYER_LIST.write().unwrap().push(PlayerState::new(username.to_string(), eid));
info!("{username} logged in. Protocol version {protocol_version}"); info!("{username} logged in. Protocol version {protocol_version}");
*player_state = PlayerState::new(username.to_string(), eid);
for i in -10..10 { for i in -10..10 {
for o 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_u8(Command::SpawnPosition as u8)?;
connection.write_u32::<BE>(0)?; connection.write_u32::<BE>(0)?;
connection.write_u32::<BE>(70)?; connection.write_u32::<BE>(0)?;
connection.write_u32::<BE>(0)?; connection.write_u32::<BE>(0)?;
let playerpos = PlayerPositionAndLook { let playerpos = PlayerPositionLook {
position: PlayerPosition { position: PlayerPosition {
position_x: 1.0, position_x: 0.5,
stance: 0.0, stance: 0.0,
position_y: 9.63, position_y: 9.63,
position_z: 1.0, position_z: 0.5,
}, },
look: PlayerLook { look: PlayerLook {
yaw: 0.0, yaw: 0.0,
@ -124,19 +165,28 @@ fn handle_command(mut connection: &mut TcpStream, command: Command) -> Result<()
connection.write_all(&playerpos.to_bytes())?; connection.write_all(&playerpos.to_bytes())?;
}, },
Command::ChatMessage => { Command::ChatMessage => {
info!("Chat Message Recieved: {}", connection.read_mcstring().unwrap()); let message = connection.read_mcstring().unwrap();
info!("Chat Message Recieved: {message}");
} }
Command::Player => { Command::Player => {
connection.read_u8()?; connection.read_u8()?;
}, },
Command::PlayerLook => { Command::PlayerLook => {
let _look = PlayerLook::from_bytes(&mut connection); let look = PlayerLook::from_bytes(&mut connection);
player_state.set_look(look);
} }
Command::PlayerPosition => { Command::PlayerPosition => {
let _pos = PlayerPosition::from_bytes(&mut connection); let pos = PlayerPosition::from_bytes(&mut connection);
player_state.set_position(pos);
} }
Command::PlayerPositionAndLook => { 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::<BE>()?;
let block_id = connection.read_i16::<BE>()?;
} }
Command::PlayerDigging => { Command::PlayerDigging => {
let _status = DiggingStatus::from_u8(connection.read_u8()?).unwrap(); 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 => { Command::PlayerBlockPlacement => {
let _status = PlayerBlockPlacement::from_bytes(&mut connection); let _status = PlayerBlockPlacement::from_bytes(&mut connection);
dbg!(_status);
} }
Command::ArmAnimation => { Command::Animation => {
let _eid = connection.read_i32::<BE>()?; let _eid = connection.read_i32::<BE>()?;
let _animate = connection.read_u8()? != 0; let _animate = connection.read_u8()?;
dbg!(_animate);
} }
Command::Disconnect => { Command::Disconnect => {
let disconnect_string = connection.read_mcstring()?; 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)?; connection.shutdown(std::net::Shutdown::Both)?;
} }
Command::KeepAlive => { Command::KeepAlive => {
let _ = connection.write_u8(0x00); connection.write_u8(Command::KeepAlive as u8)?;
} }
Command::UpdateHealth => { Command::UpdateHealth => {
connection.read_u8()?; 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(()) Ok(())
@ -190,9 +238,26 @@ enum Command {
PlayerPositionAndLook = 0x0D, PlayerPositionAndLook = 0x0D,
PlayerDigging = 0x0E, PlayerDigging = 0x0E,
PlayerBlockPlacement = 0x0F, 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, PreChunk = 0x32,
MapChunk = 0x33, MapChunk = 0x33,
BlockChange = 0x35,
ComplexEntities = 0x3B,
Disconnect = 0xFF, Disconnect = 0xFF,
} }

View file

@ -1,6 +1,6 @@
use std::io::Read; use std::io::Read;
use byteorder::{ReadBytesExt, WriteBytesExt, BE}; use byteorder::{ReadBytesExt, BE};
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;

View file

@ -4,13 +4,13 @@ use byteorder::{ReadBytesExt, WriteBytesExt, BE};
use crate::byte_ops::ToBytes; use crate::byte_ops::ToBytes;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct PlayerPositionAndLook { pub struct PlayerPositionLook {
pub position: PlayerPosition, pub position: PlayerPosition,
pub look: PlayerLook, pub look: PlayerLook,
} }
impl ToBytes for PlayerPositionAndLook { impl ToBytes for PlayerPositionLook {
type Bytes = Vec<u8>; type Bytes = Vec<u8>;
fn to_bytes(self) -> Self::Bytes { fn to_bytes(self) -> Self::Bytes {
@ -27,7 +27,7 @@ impl ToBytes for PlayerPositionAndLook {
} }
} }
impl PlayerPositionAndLook { impl PlayerPositionLook {
pub fn from_bytes<R: Read>(stream: &mut R) -> Self { pub fn from_bytes<R: Read>(stream: &mut R) -> Self {
let position_x = stream.read_f64::<BE>().unwrap(); let position_x = stream.read_f64::<BE>().unwrap();
let position_y = stream.read_f64::<BE>().unwrap(); let position_y = stream.read_f64::<BE>().unwrap();
@ -54,7 +54,7 @@ impl PlayerPositionAndLook {
} }
} }
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct PlayerPosition { pub struct PlayerPosition {
pub position_x: f64, pub position_x: f64,
pub position_y: 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 struct PlayerLook {
pub yaw: f32, pub yaw: f32,
pub pitch: f32, pub pitch: f32,

View file

@ -1,16 +1,36 @@
use std::collections::BTreeMap;
use crate::{ use crate::{
chunk::MapChunk, position::{PlayerLook, PlayerPosition}, chunk::{BlockType, MapChunk},
position::{PlayerLook, PlayerPosition, PlayerPositionLook},
}; };
pub struct GameState { pub struct GameState {
player_list: BTreeMap<String, PlayerState>,
} }
impl GameState {
pub fn new() -> Self {
Self {
player_list: BTreeMap::new()
}
}
pub fn player_list(&self) -> &BTreeMap<String, PlayerState> {
&self.player_list
}
pub fn player_list_mut(&mut self) -> &mut BTreeMap<String, PlayerState> {
&mut self.player_list
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PlayerState { pub struct PlayerState {
eid: i32, eid: i32,
username: String, username: String,
position: PlayerPosition, holding: i16,
look: PlayerLook, position_look: PlayerPositionLook,
} }
impl PlayerState { impl PlayerState {
@ -19,10 +39,59 @@ impl PlayerState {
Self { Self {
eid, eid,
username, username,
position: PlayerPosition::default(), holding: -1,
look: PlayerLook::default(), 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 { pub struct WorldState {