mirror of https://github.com/zyllian/classics.git synced 2025-05-10 05:06:41 -07:00

initial commit

This commit is contained in:
Zoey 2024-04-18 19:43:27 -07:00
commit ca94ec10f2
No known key found for this signature in database
GPG key ID: 8611B896D1AAFAF2
12 changed files with 1624 additions and 0 deletions

41
src/level.rs Normal file
View file

@ -0,0 +1,41 @@
/// a classic level
#[derive(Debug, Clone)]
pub struct Level {
/// the size of the level in the X direction
pub x_size: usize,
/// the size of the level in the Y direction
pub y_size: usize,
/// the size of the level in the Z direction
pub z_size: usize,
/// the blocks which make up the level
pub blocks: Vec<u8>,
}
impl Level {
/// creates a new level with the given dimensions
pub fn new(x_size: usize, y_size: usize, z_size: usize) -> Self {
Self {
x_size,
y_size,
z_size,
blocks: vec![0; x_size * y_size * z_size],
}
}
/// gets the index for a given block position
pub fn index(&self, x: usize, y: usize, z: usize) -> usize {
x + z * self.x_size + y * self.x_size * self.z_size
}
/// gets the block at the given position
pub fn get_block(&self, x: usize, y: usize, z: usize) -> u8 {
self.blocks[self.index(x, y, z)]
}
/// sets the block at the given position
pub fn set_block(&mut self, x: usize, y: usize, z: usize, block: u8) {
let index = self.index(x, y, z);
self.blocks[index] = block;
}
}

15
src/main.rs Normal file
View file

@ -0,0 +1,15 @@
use server::Server;
mod level;
mod packet;
mod player;
mod server;
#[tokio::main]
async fn main() -> std::io::Result<()> {
let mut server = Server::new().await?;
server.run().await?;
Ok(())
}

159
src/packet.rs Normal file
View file

@ -0,0 +1,159 @@
use half::f16;
pub mod client;
pub mod server;
/// length of classic strings
pub const STRING_LENGTH: usize = 64;
/// length of classic level chunk arrays
pub const ARRAY_LENGTH: usize = 1024;
/// units in an f16 unit
pub const F16_UNITS: f32 = 32.0;
/// helper for reading packets
#[derive(Debug)]
pub struct PacketReader<'p> {
raw_packet: &'p [u8],
cursor: usize,
}
impl<'p> PacketReader<'p> {
/// creates a new packet reader from the given packet data
pub fn new(raw_packet: &'p [u8]) -> Self {
Self {
raw_packet,
cursor: 0,
}
}
/// gets the next u8 in the packet, if any
fn next_u8(&mut self) -> Option<u8> {
let r = self.raw_packet.get(self.cursor).copied();
self.cursor = self.cursor.checked_add(1).unwrap_or(self.cursor);
r
}
/// gets the next i8 in the packet, if any
fn next_i8(&mut self) -> Option<i8> {
self.next_u8().map(|b| b as i8)
}
/// gets the next u16 in the packet, if any
fn next_u16(&mut self) -> Option<u16> {
Some(u16::from_be_bytes([self.next_u8()?, self.next_u8()?]))
}
/// gets the next i16 in the packet, if any
fn next_i16(&mut self) -> Option<i16> {
self.next_u16().map(|s| s as i16)
}
/// gets the next f16 in the packet, if any
fn next_f16(&mut self) -> Option<f16> {
self.next_i16().map(|v| f16::from_f32(v as f32 / F16_UNITS))
}
/// gets the next string in the packet, if any
fn next_string(&mut self) -> Option<String> {
let mut chars: Vec<char> = Vec::new();
for _ in 0..STRING_LENGTH {
chars.push(self.next_u8()? as char);
}
Some(String::from_iter(chars).trim().to_string())
}
/// gets the next array of the given length in the packet, if any
fn next_array_of_length(&mut self, len: usize) -> Option<Vec<u8>> {
let mut bytes: Vec<u8> = Vec::new();
let mut append = true;
for _ in 0..len {
let b = self.next_u8()?;
if append {
if b == 0 {
append = false;
} else {
bytes.push(b);
}
}
}
Some(bytes)
}
/// gets the next array of default size in the packet, if any
fn next_array(&mut self) -> Option<Vec<u8>> {
self.next_array_of_length(ARRAY_LENGTH)
}
}
/// helper for writing a packet
#[derive(Debug, Default)]
pub struct PacketWriter {
raw_packet: Vec<u8>,
}
impl PacketWriter {
/// gets the actual raw packet data from the writer
pub fn into_raw_packet(self) -> Vec<u8> {
self.raw_packet
}
/// writes a u8 to the packet
pub fn write_u8(mut self, b: u8) -> Self {
self.raw_packet.push(b);
self
}
/// writes an i8 to the packet
fn write_i8(self, b: i8) -> Self {
self.write_u8(b as u8)
}
/// writes a u16 to the packet
fn write_u16(self, sh: u16) -> Self {
let mut s = self;
for b in sh.to_be_bytes() {
s = s.write_u8(b);
}
s
}
/// writes an i16 to the packet
fn write_i16(self, sh: i16) -> Self {
self.write_u16(sh as u16)
}
/// writes an f16 to the packet
fn write_f16(self, f: f16) -> Self {
let r = (f.to_f32() * F16_UNITS) as i16;
self.write_i16(r)
}
/// writes a string to the packet
fn write_string(self, str: &str) -> Self {
let mut s = self;
for b in str
.as_bytes()
.iter()
.copied()
.chain(Some(0x20).into_iter().cycle())
.take(STRING_LENGTH)
{
s = s.write_u8(b);
}
s
}
/// writes an array of the given length to the packet
fn write_array_of_length(self, bytes: &[u8], len: usize) -> Self {
let mut s = self;
for i in 0..len {
s = s.write_u8(bytes.get(i).copied().unwrap_or_default());
}
s
}
/// writes an array of default length to the packet
fn write_array(self, bytes: &[u8]) -> Self {
self.write_array_of_length(bytes, ARRAY_LENGTH)
}
}

133
src/packet/client.rs Normal file
View file

@ -0,0 +1,133 @@
use half::f16;
/// enum for a packet which can be received by the client
#[derive(Debug, Clone)]
pub enum ClientPacket {
/// packet sent by a client to identify itself to the server
PlayerIdentification {
/// should always be 0x07 for classic clients >= 0.28
protocol_version: u8,
username: String,
/// currently unverified, original minecraft auth for classic is gone anyway
/// TODO: use verification key field as password protection? investigate
verification_key: String,
_unused: u8,
},
/// packet sent when a client changes a block
/// because changes are reflected immediately, to restrict changes, server must send back its own SetBlock packet with the original block
SetBlock {
x: i16,
y: i16,
z: i16,
/// 0x00 for destroy, 0x01 for create
mode: u8,
block_type: u8,
},
/// sent to update the player's current position and orientation with the server
PositionOrientation {
/// should always be 0xff (-1), referring to the player who sent it
_player_id: i8,
x: f16,
y: f16,
z: f16,
yaw: u8,
pitch: u8,
},
/// packet for the client to send chat messages
Message {
/// should always be 0xff (-1), referring to the player who sent it
player_id: i8,
message: String,
},
}
impl ClientPacket {
// unused currently, so disabled
// /// gets the packet's id
// pub fn get_id(&self) -> u8 {
// match self {
// Self::PlayerIdentification { .. } => 0x00,
// Self::SetBlock { .. } => 0x05,
// Self::PositionOrientation { .. } => 0x08,
// Self::Message { .. } => 0x0d,
// }
// }
/// reads the packet
pub fn read(id: u8, packet: &mut super::PacketReader) -> Option<Self> {
Some(match id {
0x00 => Self::PlayerIdentification {
protocol_version: packet.next_u8()?,
username: packet.next_string()?,
verification_key: packet.next_string()?,
_unused: packet.next_u8()?,
},
0x05 => Self::SetBlock {
x: packet.next_i16()?,
y: packet.next_i16()?,
z: packet.next_i16()?,
mode: packet.next_u8()?,
block_type: packet.next_u8()?,
},
0x08 => Self::PositionOrientation {
_player_id: packet.next_i8()?,
x: packet.next_f16()?,
y: packet.next_f16()?,
z: packet.next_f16()?,
yaw: packet.next_u8()?,
pitch: packet.next_u8()?,
},
0x0d => Self::Message {
player_id: packet.next_i8()?,
message: packet.next_string()?,
},
_ => return None,
})
}
// only needed on the client, so disabled for now
// /// writes the packet
// pub fn write(&self, writer: super::PacketWriter) -> super::PacketWriter {
// match self {
// Self::PlayerIdentification {
// protocol_version,
// username,
// verification_key,
// _unused,
// } => writer
// .write_u8(*protocol_version)
// .write_string(username)
// .write_string(verification_key)
// .write_u8(*_unused),
// Self::SetBlock {
// x,
// y,
// z,
// mode,
// block_type,
// } => writer
// .write_i16(*x)
// .write_i16(*y)
// .write_i16(*z)
// .write_u8(*mode)
// .write_u8(*block_type),
// Self::PositionOrientation {
// player_id,
// x,
// y,
// z,
// yaw,
// pitch,
// } => writer
// .write_i8(*player_id)
// .write_f16(*x)
// .write_f16(*y)
// .write_f16(*z)
// .write_u8(*yaw)
// .write_u8(*pitch),
// Self::Message { player_id, message } => {
// writer.write_i8(*player_id).write_string(message)
// }
// }
// }
}

262
src/packet/server.rs Normal file
View file

@ -0,0 +1,262 @@
use half::f16;
use crate::player::PlayerType;
#[derive(Debug, Clone)]
#[allow(unused)]
pub enum ServerPacket {
/// packet sent as a response to joining clients
ServerIdentification {
/// should be 0x07
protocol_version: u8,
server_name: String,
server_motd: String,
user_type: PlayerType,
},
/// since clients do not notify the server when leaving, the ping packet is used to check if the client is still connected
/// TODO: implement pinging? classicube works fine without it
Ping {},
/// informs clients that there is incoming level data
LevelInitialize {},
/// packet to send a chunk (not minecraft chunk) of gzipped level data
LevelDataChunk {
chunk_length: i16,
chunk_data: Vec<u8>,
percent_complete: u8,
},
/// packet sent after chunk data is finished sending containing the level dimensions
LevelFinalize {
x_size: i16,
y_size: i16,
z_size: i16,
},
/// indicates a block change
/// when a player changes a block, their own change is echoed back to them
SetBlock {
x: i16,
y: i16,
z: i16,
block_type: u8,
},
/// packet sent when a new player spawns
/// also contains their spawn point
SpawnPlayer {
player_id: i8,
player_name: String,
x: f16,
y: f16,
z: f16,
yaw: u8,
pitch: u8,
},
/// packet to set a player's position and orientation
SetPositionOrientation {
player_id: i8,
x: f16,
y: f16,
z: f16,
yaw: u8,
pitch: u8,
},
/// packet to update a player's position and orientation
/// TODO: implement?
UpdatePositionOrientation {
player_id: i8,
x_change: f16,
y_change: f16,
z_change: f16,
yaw: u8,
pitch: u8,
},
/// packet to update a player's position
/// TODO: implement?
UpdatePosition {
player_id: i8,
x_change: f16,
y_change: f16,
z_change: f16,
},
/// packet to update a player's orientation
/// TODO: implement?
UpdateOrientation { player_id: i8, yaw: u8, pitch: u8 },
/// packet sent when a player is despawned from the world (i.e. when leaving)
DespawnPlayer { player_id: i8 },
/// packet sent when there's a chat message to go out
Message { player_id: i8, message: String },
/// informs a client that they're being disconnected from the server and why
DisconnectPlayer { disconnect_reason: String },
/// packet sent to a user to inform them that their user type has changed
UpdateUserType {
/// 0x00 for normal, 0x64 for op
user_type: PlayerType,
},
}
impl ServerPacket {
/// gets the packet's id
pub fn get_id(&self) -> u8 {
match self {
Self::ServerIdentification { .. } => 0x00,
Self::Ping {} => 0x01,
Self::LevelInitialize {} => 0x02,
Self::LevelDataChunk { .. } => 0x03,
Self::LevelFinalize { .. } => 0x04,
Self::SetBlock { .. } => 0x06,
Self::SpawnPlayer { .. } => 0x07,
Self::SetPositionOrientation { .. } => 0x08,
Self::UpdatePositionOrientation { .. } => 0x09,
Self::UpdatePosition { .. } => 0x0a,
Self::UpdateOrientation { .. } => 0x0b,
Self::DespawnPlayer { .. } => 0x0c,
Self::Message { .. } => 0x0d,
Self::DisconnectPlayer { .. } => 0x0e,
Self::UpdateUserType { .. } => 0x0f,
}
}
/// writes the packet
pub fn write(&self, writer: super::PacketWriter) -> super::PacketWriter {
match self {
Self::ServerIdentification {
protocol_version,
server_name,
server_motd,
user_type,
} => writer
.write_u8(*protocol_version)
.write_string(server_name)
.write_string(server_motd)
.write_u8(*user_type as u8),
Self::Ping {} => writer,
Self::LevelInitialize {} => writer,
Self::LevelDataChunk {
chunk_length,
chunk_data,
percent_complete,
} => writer
.write_i16(*chunk_length)
.write_array(chunk_data)
.write_u8(*percent_complete),
Self::LevelFinalize {
x_size,
y_size,
z_size,
} => writer
.write_i16(*x_size)
.write_i16(*y_size)
.write_i16(*z_size),
Self::SetBlock {
x,
y,
z,
block_type,
} => writer
.write_i16(*x)
.write_i16(*y)
.write_i16(*z)
.write_u8(*block_type),
Self::SpawnPlayer {
player_id,
player_name,
x,
y,
z,
yaw,
pitch,
} => writer
.write_i8(*player_id)
.write_string(player_name)
.write_f16(*x)
.write_f16(*y)
.write_f16(*z)
.write_u8(*yaw)
.write_u8(*pitch),
Self::SetPositionOrientation {
player_id,
x,
y,
z,
yaw,
pitch,
} => writer
.write_i8(*player_id)
.write_f16(*x)
.write_f16(*y)
.write_f16(*z)
.write_u8(*yaw)
.write_u8(*pitch),
Self::UpdatePositionOrientation {
player_id,
x_change,
y_change,
z_change,
yaw,
pitch,
} => writer
.write_i8(*player_id)
.write_f16(*x_change)
.write_f16(*y_change)
.write_f16(*z_change)
.write_u8(*yaw)
.write_u8(*pitch),
Self::UpdatePosition {
player_id,
x_change,
y_change,
z_change,
} => writer
.write_i8(*player_id)
.write_f16(*x_change)
.write_f16(*y_change)
.write_f16(*z_change),
Self::UpdateOrientation {
player_id,
yaw,
pitch,
} => writer.write_i8(*player_id).write_u8(*yaw).write_u8(*pitch),
Self::DespawnPlayer { player_id } => writer.write_i8(*player_id),
Self::Message { player_id, message } => {
writer.write_i8(*player_id).write_string(message)
}
Self::DisconnectPlayer { disconnect_reason } => writer.write_string(disconnect_reason),
Self::UpdateUserType { user_type } => writer.write_u8(*user_type as u8),
}
}
/// gets the player id contained in the packet, if any
pub fn get_player_id(&self) -> Option<i8> {
Some(match self {
Self::SpawnPlayer { player_id, .. } => *player_id,
Self::SetPositionOrientation { player_id, .. } => *player_id,
Self::UpdatePositionOrientation { player_id, .. } => *player_id,
Self::UpdatePosition { player_id, .. } => *player_id,
Self::UpdateOrientation { player_id, .. } => *player_id,
Self::DespawnPlayer { player_id, .. } => *player_id,
Self::Message { player_id, .. } => *player_id,
_ => return None,
})
}
/// sets the player id in the packet if possible
pub fn set_player_id(&mut self, new_player_id: i8) {
match self {
Self::SpawnPlayer { player_id, .. } => *player_id = new_player_id,
Self::SetPositionOrientation { player_id, .. } => *player_id = new_player_id,
Self::UpdatePositionOrientation { player_id, .. } => *player_id = new_player_id,
Self::UpdatePosition { player_id, .. } => *player_id = new_player_id,
Self::UpdateOrientation { player_id, .. } => *player_id = new_player_id,
Self::DespawnPlayer { player_id, .. } => *player_id = new_player_id,
Self::Message { player_id, .. } => *player_id = new_player_id,
_ => {}
}
}
/// gets whether this packet should echo back to the current player
pub fn should_echo(&self) -> bool {
matches!(
self,
Self::SetBlock { .. } | Self::SpawnPlayer { .. } | Self::Message { .. }
)
}
}

61
src/player.rs Normal file
View file

@ -0,0 +1,61 @@
use std::net::SocketAddr;
use half::f16;
use crate::packet::server::ServerPacket;
/// struct for players
#[derive(Debug)]
pub struct Player {
/// the player's id
pub id: i8,
/// the player's username
pub username: String,
/// the player's X coordinate
pub x: f16,
/// the player's Y coordinate
pub y: f16,
/// the player's Z coordinate
pub z: f16,
/// the player's yaw
pub yaw: u8,
/// the player's pitch
pub pitch: u8,
/// the player's permission state
pub player_type: PlayerType,
/// the player's IP address
pub _addr: SocketAddr,
/// queue of packets to be sent to this player
pub packets_to_send: Vec<ServerPacket>,
}
/// enum describing types of players
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum PlayerType {
/// a normal player
Normal = 0x00,
/// a player who's an operator
Operator = 0x64,
}
impl Default for PlayerType {
fn default() -> Self {
Self::Normal
}
}
impl TryFrom<u8> for PlayerType {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value == Self::Normal as u8 {
Ok(Self::Normal)
} else if value == Self::Operator as u8 {
Ok(Self::Operator)
} else {
Err(())
}
}
}

82
src/server.rs Normal file
View file

@ -0,0 +1,82 @@
mod network;
use std::sync::Arc;
// use parking_lot::RwLock;
use rand::Rng;
use tokio::{net::TcpListener, sync::RwLock};
use crate::{level::Level, player::Player};
const DEFAULT_SERVER_SIZE: usize = 128;
/// the server
#[derive(Debug)]
pub struct Server {
/// shared server data
pub data: Arc<RwLock<ServerData>>,
/// the server's listener
pub listener: TcpListener,
}
/// shared server data
#[derive(Debug)]
pub struct ServerData {
/// the level
pub level: Level,
/// list of players connected to the server
pub players: Vec<Player>,
/// list of player ids which have been freed up
pub free_player_ids: Vec<i8>,
}
impl Server {
/// creates a new server with a generated level
pub async fn new() -> std::io::Result<Self> {
println!("generating level");
let mut rng = rand::thread_rng();
let mut level = Level::new(
DEFAULT_SERVER_SIZE,
DEFAULT_SERVER_SIZE,
DEFAULT_SERVER_SIZE,
);
for x in 0..level.x_size {
for y in 0..(level.y_size / 2) {
for z in 0..level.z_size {
level.set_block(x, y, z, rng.gen_range(0..50));
}
}
}
println!("done!");
Self::new_with_level(level).await
}
/// creates a new server with the given level
pub async fn new_with_level(level: Level) -> std::io::Result<Self> {
let listener = TcpListener::bind("127.0.0.1:25565").await?;
Ok(Self {
data: Arc::new(RwLock::new(ServerData {
level,
players: Default::default(),
free_player_ids: Vec::new(),
})),
listener,
})
}
/// starts the server
pub async fn run(&mut self) -> std::io::Result<()> {
loop {
let (stream, addr) = self.listener.accept().await?;
println!("connection from {addr}");
let data = self.data.clone();
tokio::spawn(async move {
network::handle_stream(stream, addr, data)
.await
.expect("failed to handle client stream");
});
}
}
}

344
src/server/network.rs Normal file
View file

@ -0,0 +1,344 @@
use std::{collections::VecDeque, io::Write, net::SocketAddr, sync::Arc};
use flate2::{write::GzEncoder, Compression};
use half::f16;
use tokio::{
io::{AsyncWriteExt, Interest},
net::TcpStream,
sync::RwLock,
};
use crate::{
level::Level,
packet::{
client::ClientPacket, server::ServerPacket, PacketReader, PacketWriter, ARRAY_LENGTH,
},
player::{Player, PlayerType},
};
use super::ServerData;
pub(super) async fn handle_stream(
mut stream: TcpStream,
addr: SocketAddr,
data: Arc<RwLock<ServerData>>,
) -> std::io::Result<()> {
let mut own_id: i8 = -1;
let r = handle_stream_inner(&mut stream, addr, data.clone(), &mut own_id).await;
match r {
Ok(disconnect_reason) => {
if let Some(disconnect_reason) = disconnect_reason {
let packet = ServerPacket::DisconnectPlayer { disconnect_reason };
let writer = PacketWriter::default().write_u8(packet.get_id());
let msg = packet.write(writer).into_raw_packet();
if let Err(e) = stream.write_all(&msg).await {
eprintln!("Failed to write disconnect packet for <{addr}>: {e}");
}
}
}
Err(e) => eprintln!("Error in stream handler for <{addr}>: {e}"),
}
if let Err(e) = stream.shutdown().await {
eprintln!("Failed to properly shut down stream for <{addr}>: {e}");
}
let mut data = data.write().await;
if let Some(index) = data.players.iter().position(|p| p.id == own_id) {
let player = data.players.remove(index);
let despawn_packet = ServerPacket::DespawnPlayer { player_id: own_id };
let message_packet = ServerPacket::Message {
player_id: own_id,
message: format!("&e{} has left the server.", player.username),
};
for player in &mut data.players {
player.packets_to_send.push(despawn_packet.clone());
player.packets_to_send.push(message_packet.clone());
}
}
Ok(())
}
async fn handle_stream_inner(
stream: &mut TcpStream,
addr: SocketAddr,
data: Arc<RwLock<ServerData>>,
own_id: &mut i8,
) -> std::io::Result<Option<String>> {
const BUF_SIZE: usize = 130;
let mut reply_queue: VecDeque<ServerPacket> = VecDeque::new();
let mut packet_buf = [0u8];
let mut read_buf;
loop {
let ready = stream
.ready(Interest::READABLE | Interest::WRITABLE)
.await?;
if ready.is_read_closed() {
println!("disconnecting {addr}");
break;
}
if ready.is_readable() {
match stream.try_read(&mut packet_buf) {
Ok(n) => {
if n == 1 {
read_buf = [0; BUF_SIZE];
match stream.try_read(&mut read_buf) {
Ok(_n) => {}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
Err(e) => return Err(e),
}
let mut reader = PacketReader::new(&read_buf);
if let Some(packet) = ClientPacket::read(packet_buf[0], &mut reader) {
// println!("{packet:#?}");
match packet {
ClientPacket::PlayerIdentification {
protocol_version,
username,
verification_key: _,
_unused,
} => {
if protocol_version != 0x07 {
return Ok(Some("Unknown protocol version! Please connect with a classic 0.30-compatible client.".to_string()));
}
let zero = f16::from_f32(0.0);
let mut data = data.write().await;
for player in &data.players {
if player.username == username {
return Ok(Some(
"Player with username already connected!"
.to_string(),
));
}
}
*own_id = data
.free_player_ids
.pop()
.unwrap_or_else(|| data.players.len() as i8);
let player = Player {
_addr: addr,
id: *own_id, // TODO: actually assign user ids
username,
// TODO: properly assign spawn stuff
x: zero,
y: zero,
z: zero,
yaw: 0,
pitch: 0,
player_type: PlayerType::Normal,
packets_to_send: Vec::new(),
};
reply_queue.push_back(ServerPacket::ServerIdentification {
protocol_version: 0x07,
server_name: "test server".to_string(),
server_motd: "whoaaaaaa".to_string(),
user_type: PlayerType::Normal,
});
println!("generating level packets");
reply_queue
.extend(build_level_packets(&data.level).into_iter());
let username = player.username.clone();
data.players.push(player);
let spawn_packet = ServerPacket::SpawnPlayer {
player_id: *own_id,
player_name: username.clone(),
x: f16::from_f32(16.5),
y: f16::from_f32((data.level.y_size / 2 + 2) as f32),
z: f16::from_f32(16.5),
yaw: 0,
pitch: 0,
};
let message_packet = ServerPacket::Message {
player_id: *own_id,
message: format!("&e{} has joined the server.", username),
};
for player in &mut data.players {
player.packets_to_send.push(spawn_packet.clone());
if player.id != *own_id {
reply_queue.push_back(ServerPacket::SpawnPlayer {
player_id: player.id,
player_name: player.username.clone(),
x: player.x,
y: player.y,
z: player.z,
yaw: player.yaw,
pitch: player.pitch,
});
player.packets_to_send.push(message_packet.clone());
}
}
reply_queue.push_back(ServerPacket::Message {
player_id: *own_id,
message: "Welcome to the server! Enjoyyyyyy".to_string(),
});
reply_queue.push_back(ServerPacket::UpdateUserType {
user_type: PlayerType::Operator,
});
}
ClientPacket::SetBlock {
x,
y,
z,
mode,
block_type,
} => {
let block_type = if mode == 0x00 { 0 } else { block_type };
let mut data = data.write().await;
let block =
data.level.get_block(x as usize, y as usize, z as usize);
// check if bedrock
if block == 0x07
&& data
.players
.iter()
.find_map(|p| {
(p.id == *own_id).then_some(p.player_type)
})
.unwrap_or_default() != PlayerType::Operator
{
continue;
}
let packet = ServerPacket::SetBlock {
x,
y,
z,
block_type,
};
data.level
.set_block(x as usize, y as usize, z as usize, block_type);
for player in &mut data.players {
player.packets_to_send.push(packet.clone());
}
}
ClientPacket::PositionOrientation {
_player_id: _,
x,
y,
z,
yaw,
pitch,
} => {
let packet = ServerPacket::SetPositionOrientation {
player_id: *own_id,
x,
y,
z,
yaw,
pitch,
};
let mut data = data.write().await;
for player in &mut data.players {
player.packets_to_send.push(packet.clone());
}
}
ClientPacket::Message { player_id, message } => {
let mut data = data.write().await;
println!("{message}");
let message = format!(
"&f<{}> {message}",
data.players
.iter()
.find(|p| p.id == *own_id)
.expect("should never fail")
.username
);
let packet = ServerPacket::Message { player_id, message };
for player in &mut data.players {
player.packets_to_send.push(packet.clone());
}
}
}
} else {
println!("unknown packet id: {:0x}", packet_buf[0]);
}
}
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
Err(e) => return Err(e),
}
}
if ready.is_writable() {
{
let mut data = data.write().await;
if let Some(player) = data.players.iter_mut().find(|p| p.id == *own_id) {
for mut packet in player.packets_to_send.drain(..) {
if let Some(id) = packet.get_player_id() {
if id == *own_id {
if !packet.should_echo() {
continue;
}
packet.set_player_id(-1);
}
}
reply_queue.push_back(packet);
}
}
}
while let Some(packet) = reply_queue.pop_front() {
let writer = PacketWriter::default().write_u8(packet.get_id());
let msg = packet.write(writer).into_raw_packet();
stream.write_all(&msg).await?;
}
}
}
println!("remaining packets: {}", reply_queue.len());
Ok(None)
}
/// helper to put together packets that need to be sent to send full level data for the given level
fn build_level_packets(level: &Level) -> Vec<ServerPacket> {
let mut packets: Vec<ServerPacket> = vec![ServerPacket::LevelInitialize {}];
// TODO: the type conversions in here may be weird idk
let volume = level.x_size * level.y_size * level.z_size;
let mut data = Vec::with_capacity(volume + 4);
data.extend_from_slice(&(volume as i32).to_be_bytes());
data.extend_from_slice(&level.blocks);
let mut e = GzEncoder::new(Vec::new(), Compression::best());
e.write_all(&data).expect("failed to gzip level data");
let data = e.finish().expect("failed to gzip level data");
let data_len = data.len();
let mut total_bytes = 0;
for chunk in data.chunks(ARRAY_LENGTH) {
let chunk_len = chunk.len();
let percent_complete = (total_bytes * 100 / data_len) as u8;
packets.push(ServerPacket::LevelDataChunk {
chunk_length: chunk_len as i16,
chunk_data: chunk.to_vec(),
percent_complete,
});
total_bytes += chunk_len;
}
packets.push(ServerPacket::LevelFinalize {
x_size: level.x_size as i16,
y_size: level.y_size as i16,
z_size: level.z_size as i16,
});
packets
}