Cleanup and work on state

This commit is contained in:
G2-Games 2024-10-10 20:14:55 -05:00
parent afd0a48527
commit 33a0b37564
5 changed files with 199 additions and 98 deletions

View file

@ -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<u8>,
blocks: Vec<u8>,
metadata: Vec<u8>,
block_light: Vec<u8>,
sky_light: Vec<u8>,
}
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<u8> {
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::<BE>(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::<BE>(block_buf.len() as i32).unwrap();
buffer.write_all(&block_buf).unwrap();
buffer
}

View file

@ -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<Vec<MapChunk>> = 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<Vec<PlayerState>> = 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<i32> = 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::<u128>();
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::<BE>()?;
let username = connection.read_mcstring()?;
let password = connection.read_mcstring()?;
let map_seed = connection.read_i64::<BE>()?;
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::<BE>()?;
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::<BE>()?;
let pos_y = connection.read_u8()?;
let pos_z = connection.read_i32::<BE>()?;
let face = connection.read_u8()?;
let _status = DiggingStatus::from_u8(connection.read_u8()?).unwrap();
let _pos_x = connection.read_i32::<BE>()?;
let _pos_y = connection.read_u8()?;
let _pos_z = connection.read_i32::<BE>()?;
let _face = connection.read_u8()?;
}
Command::PlayerBlockPlacement => {
let _status = PlayerBlockPlacement::from_bytes(&mut connection);
dbg!(_status);
}
Command::ArmAnimation => {
let eid = connection.read_i32::<BE>()?;
let animate = connection.read_u8()? != 0;
let _eid = connection.read_i32::<BE>()?;
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<u8>;

68
src/player.rs Normal file
View file

@ -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<R: Read>(stream: &mut R) -> Self {
let block_id = BlockType::from_i16(stream.read_i16::<BE>().unwrap()).unwrap();
let position_x = stream.read_i32::<BE>().unwrap();
let position_y = stream.read_u8().unwrap();
let position_z = stream.read_i32::<BE>().unwrap();
let direction = Direction::from_u8(stream.read_u8().unwrap()).unwrap();
Self {
block_id,
position_x,
position_y,
position_z,
direction,
}
}
}

View file

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

30
src/state.rs Normal file
View file

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