mirror of
https://github.com/G2-Games/minecraft_alpha_server.git
synced 2025-04-18 23:03:00 -05:00
Cleanup and work on state
This commit is contained in:
parent
afd0a48527
commit
33a0b37564
5 changed files with 199 additions and 98 deletions
94
src/chunk.rs
94
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<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
|
||||
}
|
||||
|
|
101
src/main.rs
101
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<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
68
src/player.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
30
src/state.rs
Normal 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>,
|
||||
}
|
Loading…
Reference in a new issue