mirror of
https://github.com/zyllian/classics.git
synced 2025-01-18 03:32:41 -08:00
significant refactors and implement extension negotiations (resolves #17) and EnvWeatherType (resolves #29)
This commit is contained in:
parent
6cb7c5f49a
commit
bfb9d62f96
15 changed files with 756 additions and 404 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,5 +1,5 @@
|
||||||
/target
|
/target
|
||||||
server-config.json
|
server-config.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.clw
|
/levels
|
||||||
*.cw
|
*.cw
|
||||||
|
|
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -81,6 +81,16 @@ version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitmask-enum"
|
||||||
|
version = "2.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9990737a6d5740ff51cdbbc0f0503015cb30c390f6623968281eb214a520cfc0"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -104,6 +114,7 @@ name = "classics"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
|
"bitmask-enum",
|
||||||
"bytes",
|
"bytes",
|
||||||
"flate2",
|
"flate2",
|
||||||
"half",
|
"half",
|
||||||
|
|
|
@ -5,6 +5,7 @@ version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "2.0.0-rc.3"
|
bincode = "2.0.0-rc.3"
|
||||||
|
bitmask-enum = "2"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
half = "2"
|
half = "2"
|
||||||
|
|
|
@ -17,6 +17,7 @@ const CMD_BAN: &str = "ban";
|
||||||
const CMD_ALLOWENTRY: &str = "allowentry";
|
const CMD_ALLOWENTRY: &str = "allowentry";
|
||||||
const CMD_SETPASS: &str = "setpass";
|
const CMD_SETPASS: &str = "setpass";
|
||||||
const CMD_SETLEVELSPAWN: &str = "setlevelspawn";
|
const CMD_SETLEVELSPAWN: &str = "setlevelspawn";
|
||||||
|
const CMD_WEATHER: &str = "weather";
|
||||||
|
|
||||||
/// list of commands available on the server
|
/// list of commands available on the server
|
||||||
pub const COMMANDS_LIST: &[&str] = &[
|
pub const COMMANDS_LIST: &[&str] = &[
|
||||||
|
@ -30,6 +31,7 @@ pub const COMMANDS_LIST: &[&str] = &[
|
||||||
CMD_ALLOWENTRY,
|
CMD_ALLOWENTRY,
|
||||||
CMD_SETPASS,
|
CMD_SETPASS,
|
||||||
CMD_SETLEVELSPAWN,
|
CMD_SETLEVELSPAWN,
|
||||||
|
CMD_WEATHER,
|
||||||
];
|
];
|
||||||
|
|
||||||
/// enum for possible commands
|
/// enum for possible commands
|
||||||
|
@ -69,6 +71,8 @@ pub enum Command<'m> {
|
||||||
SetPass { password: &'m str },
|
SetPass { password: &'m str },
|
||||||
/// sets the level spawn to the player's location
|
/// sets the level spawn to the player's location
|
||||||
SetLevelSpawn,
|
SetLevelSpawn,
|
||||||
|
/// changes the levels weather
|
||||||
|
Weather { weather_type: &'m str },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'m> Command<'m> {
|
impl<'m> Command<'m> {
|
||||||
|
@ -117,6 +121,9 @@ impl<'m> Command<'m> {
|
||||||
password: arguments.trim(),
|
password: arguments.trim(),
|
||||||
},
|
},
|
||||||
CMD_SETLEVELSPAWN => Self::SetLevelSpawn,
|
CMD_SETLEVELSPAWN => Self::SetLevelSpawn,
|
||||||
|
CMD_WEATHER => Self::Weather {
|
||||||
|
weather_type: arguments,
|
||||||
|
},
|
||||||
_ => return Err(format!("Unknown command: {command_name}")),
|
_ => return Err(format!("Unknown command: {command_name}")),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -134,6 +141,7 @@ impl<'m> Command<'m> {
|
||||||
Self::AllowEntry { .. } => CMD_ALLOWENTRY,
|
Self::AllowEntry { .. } => CMD_ALLOWENTRY,
|
||||||
Self::SetPass { .. } => CMD_SETPASS,
|
Self::SetPass { .. } => CMD_SETPASS,
|
||||||
Self::SetLevelSpawn => CMD_SETLEVELSPAWN,
|
Self::SetLevelSpawn => CMD_SETLEVELSPAWN,
|
||||||
|
Self::Weather { .. } => CMD_WEATHER,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,6 +203,10 @@ impl<'m> Command<'m> {
|
||||||
c(""),
|
c(""),
|
||||||
"&fSets the level's spawn to your location.".to_string(),
|
"&fSets the level's spawn to your location.".to_string(),
|
||||||
],
|
],
|
||||||
|
CMD_WEATHER => vec![
|
||||||
|
c("<weather type>"),
|
||||||
|
"&fSets the level's weather.".to_string(),
|
||||||
|
],
|
||||||
_ => vec!["&eUnknown command!".to_string()],
|
_ => vec!["&eUnknown command!".to_string()],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -459,6 +471,19 @@ impl<'m> Command<'m> {
|
||||||
data.config_needs_saving = true;
|
data.config_needs_saving = true;
|
||||||
messages.push("Level spawn updated!".to_string());
|
messages.push("Level spawn updated!".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Command::Weather { weather_type } => {
|
||||||
|
if let Ok(weather_type) = weather_type.try_into() {
|
||||||
|
data.level.weather = weather_type;
|
||||||
|
let packet = ServerPacket::EnvWeatherType { weather_type };
|
||||||
|
for player in &mut data.players {
|
||||||
|
player.packets_to_send.push(packet.clone());
|
||||||
|
}
|
||||||
|
messages.push("Weather updated!".to_string());
|
||||||
|
} else {
|
||||||
|
messages.push(format!("&cUnknown weather type {weather_type}!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messages
|
messages
|
||||||
|
|
109
src/level.rs
109
src/level.rs
|
@ -1,6 +1,10 @@
|
||||||
use std::{collections::BTreeSet, path::Path};
|
use std::{
|
||||||
|
collections::BTreeSet,
|
||||||
|
io::{Read, Write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
use bincode::{Decode, Encode};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{packet::server::ServerPacket, util::neighbors};
|
use crate::{packet::server::ServerPacket, util::neighbors};
|
||||||
|
|
||||||
|
@ -9,8 +13,11 @@ use self::block::BLOCK_INFO;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod generation;
|
pub mod generation;
|
||||||
|
|
||||||
|
const LEVEL_INFO_PATH: &str = "info.json";
|
||||||
|
const LEVEL_DATA_PATH: &str = "level.dat";
|
||||||
|
|
||||||
/// a classic level
|
/// a classic level
|
||||||
#[derive(Debug, Clone, Encode, Decode)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Level {
|
pub struct Level {
|
||||||
/// the size of the level in the X direction
|
/// the size of the level in the X direction
|
||||||
pub x_size: usize,
|
pub x_size: usize,
|
||||||
|
@ -20,10 +27,15 @@ pub struct Level {
|
||||||
pub z_size: usize,
|
pub z_size: usize,
|
||||||
|
|
||||||
/// the blocks which make up the level
|
/// the blocks which make up the level
|
||||||
|
#[serde(skip)]
|
||||||
pub blocks: Vec<u8>,
|
pub blocks: Vec<u8>,
|
||||||
|
/// the level's weather
|
||||||
|
pub weather: WeatherType,
|
||||||
|
|
||||||
/// index of blocks which need to be updated in the next tick
|
/// index of blocks which need to be updated in the next tick
|
||||||
pub awaiting_update: BTreeSet<usize>,
|
pub awaiting_update: BTreeSet<usize>,
|
||||||
/// list of updates to apply to the world on the next tick
|
/// list of updates to apply to the world on the next tick
|
||||||
|
#[serde(skip)]
|
||||||
pub updates: Vec<BlockUpdate>,
|
pub updates: Vec<BlockUpdate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +47,7 @@ impl Level {
|
||||||
y_size,
|
y_size,
|
||||||
z_size,
|
z_size,
|
||||||
blocks: vec![0; x_size * y_size * z_size],
|
blocks: vec![0; x_size * y_size * z_size],
|
||||||
|
weather: WeatherType::Sunny,
|
||||||
awaiting_update: Default::default(),
|
awaiting_update: Default::default(),
|
||||||
updates: Default::default(),
|
updates: Default::default(),
|
||||||
}
|
}
|
||||||
|
@ -91,32 +104,104 @@ impl Level {
|
||||||
packets
|
packets
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn save<P>(&self, path: P)
|
/// saves the level
|
||||||
|
pub async fn save<P>(&self, path: P) -> std::io::Result<()>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
let path = path.as_ref();
|
||||||
|
tokio::fs::create_dir_all(path).await?;
|
||||||
|
tokio::fs::write(
|
||||||
|
path.join(LEVEL_INFO_PATH),
|
||||||
|
serde_json::to_string_pretty(self).unwrap(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::best());
|
let mut encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::best());
|
||||||
bincode::encode_into_std_write(self, &mut encoder, bincode::config::standard()).unwrap();
|
encoder
|
||||||
tokio::fs::write(path, encoder.finish().unwrap())
|
.write_all(&self.blocks)
|
||||||
|
.expect("failed to write blocks");
|
||||||
|
tokio::fs::write(
|
||||||
|
path.join(LEVEL_DATA_PATH),
|
||||||
|
encoder.finish().expect("failed to encode blocks"),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load<P>(path: P) -> Self
|
/// loads the level
|
||||||
|
pub async fn load<P>(path: P) -> std::io::Result<Self>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
let data = tokio::fs::read(path).await.unwrap();
|
let path = path.as_ref();
|
||||||
let mut decoder = flate2::read::GzDecoder::new(data.as_slice());
|
let mut info: Self =
|
||||||
bincode::decode_from_std_read(&mut decoder, bincode::config::standard()).unwrap()
|
serde_json::from_str(&tokio::fs::read_to_string(path.join(LEVEL_INFO_PATH)).await?)
|
||||||
|
.expect("failed to deserialize level info");
|
||||||
|
let blocks_data = tokio::fs::read(path.join(LEVEL_DATA_PATH)).await?;
|
||||||
|
let mut decoder = flate2::read::GzDecoder::new(blocks_data.as_slice());
|
||||||
|
decoder.read_to_end(&mut info.blocks)?;
|
||||||
|
let len = info.x_size * info.y_size * info.z_size;
|
||||||
|
if info.blocks.len() != len {
|
||||||
|
panic!(
|
||||||
|
"level data is not correct size! expected {len}, got {}",
|
||||||
|
info.blocks.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// struct describing a block update for the level to handle
|
/// struct describing a block update for the level to handle
|
||||||
#[derive(Debug, Clone, Encode, Decode)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct BlockUpdate {
|
pub struct BlockUpdate {
|
||||||
/// the index of the block to be updated
|
/// the index of the block to be updated
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
/// the block type to set the block to
|
/// the block type to set the block to
|
||||||
pub block: u8,
|
pub block: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// weather types for a level
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum WeatherType {
|
||||||
|
Sunny,
|
||||||
|
Raining,
|
||||||
|
Snowing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WeatherType {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Sunny
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&WeatherType> for u8 {
|
||||||
|
fn from(value: &WeatherType) -> Self {
|
||||||
|
match value {
|
||||||
|
WeatherType::Sunny => 0,
|
||||||
|
WeatherType::Raining => 1,
|
||||||
|
WeatherType::Snowing => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for WeatherType {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value {
|
||||||
|
1 => Self::Raining,
|
||||||
|
2 => Self::Snowing,
|
||||||
|
_ => Self::Sunny,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for WeatherType {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match value {
|
||||||
|
"sunny" => Self::Sunny,
|
||||||
|
"raining" => Self::Raining,
|
||||||
|
"snowing" => Self::Snowing,
|
||||||
|
_ => return Err(()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ mod player;
|
||||||
mod server;
|
mod server;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
const SERVER_NAME: &str = "classics";
|
||||||
const CONFIG_FILE: &str = "./server-config.json";
|
const CONFIG_FILE: &str = "./server-config.json";
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
134
src/packet.rs
134
src/packet.rs
|
@ -2,6 +2,7 @@ use half::f16;
|
||||||
use safer_bytes::{error::Truncated, SafeBuf};
|
use safer_bytes::{error::Truncated, SafeBuf};
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
pub mod client_extended;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
/// length of classic strings
|
/// length of classic strings
|
||||||
|
@ -10,6 +11,30 @@ pub const STRING_LENGTH: usize = 64;
|
||||||
pub const ARRAY_LENGTH: usize = 1024;
|
pub const ARRAY_LENGTH: usize = 1024;
|
||||||
/// units in an f16 unit
|
/// units in an f16 unit
|
||||||
pub const F16_UNITS: f32 = 32.0;
|
pub const F16_UNITS: f32 = 32.0;
|
||||||
|
/// the magic number to check whether the client supports extensions
|
||||||
|
pub const EXTENSION_MAGIC_NUMBER: u8 = 0x42;
|
||||||
|
|
||||||
|
/// information about a packet extension
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct ExtInfo {
|
||||||
|
/// the extension's name
|
||||||
|
pub ext_name: String,
|
||||||
|
/// the extension's version
|
||||||
|
pub version: i32,
|
||||||
|
/// the bitmask for the extension
|
||||||
|
pub bitmask: ExtBitmask,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtInfo {
|
||||||
|
/// creates new extension info
|
||||||
|
pub const fn new(ext_name: String, version: i32, bitmask: ExtBitmask) -> Self {
|
||||||
|
Self {
|
||||||
|
ext_name,
|
||||||
|
version,
|
||||||
|
bitmask,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// trait extending the `SafeBuf` type
|
/// trait extending the `SafeBuf` type
|
||||||
pub trait SafeBufExtension: SafeBuf {
|
pub trait SafeBufExtension: SafeBuf {
|
||||||
|
@ -80,6 +105,15 @@ impl PacketWriter {
|
||||||
self.write_i16(r)
|
self.write_i16(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// writes an i32 to the packet
|
||||||
|
fn write_i32(self, i: i32) -> Self {
|
||||||
|
let mut s = self;
|
||||||
|
for b in i.to_be_bytes() {
|
||||||
|
s = s.write_u8(b);
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
/// writes a string to the packet
|
/// writes a string to the packet
|
||||||
fn write_string(self, str: &str) -> Self {
|
fn write_string(self, str: &str) -> Self {
|
||||||
let mut s = self;
|
let mut s = self;
|
||||||
|
@ -109,3 +143,103 @@ impl PacketWriter {
|
||||||
self.write_array_of_length(bytes, ARRAY_LENGTH)
|
self.write_array_of_length(bytes, ARRAY_LENGTH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// bitmask for enabled extensions
|
||||||
|
/// values should not be saved to disk or sent over network! no guarantees on them remaining the same between versions
|
||||||
|
#[bitmask_enum::bitmask(u64)]
|
||||||
|
pub enum ExtBitmask {
|
||||||
|
ClickDistance,
|
||||||
|
CustomBlocks,
|
||||||
|
HeldBlock,
|
||||||
|
EmoteFix,
|
||||||
|
TextHotKey,
|
||||||
|
ExtPlayerList,
|
||||||
|
EnvColors,
|
||||||
|
SelectionCuboid,
|
||||||
|
BlockPermissions,
|
||||||
|
ChangeModel,
|
||||||
|
EnvMapAppearance,
|
||||||
|
EnvWeatherType,
|
||||||
|
HackControl,
|
||||||
|
MessageTypes,
|
||||||
|
PlayerClick,
|
||||||
|
LongerMessages,
|
||||||
|
FullCP437,
|
||||||
|
BlockDefinitions,
|
||||||
|
BlockDefinitionsExt,
|
||||||
|
BulkBlockUpdate,
|
||||||
|
TextColors,
|
||||||
|
EnvMapAspect,
|
||||||
|
EntityProperty,
|
||||||
|
ExtEntityPositions,
|
||||||
|
TwoWayPing,
|
||||||
|
InventoryOrder,
|
||||||
|
InstantMOTD,
|
||||||
|
ExtendedBlocks,
|
||||||
|
FastMap,
|
||||||
|
ExtendedTextures,
|
||||||
|
SetHotbar,
|
||||||
|
SetSpawnpoint,
|
||||||
|
VelocityControl,
|
||||||
|
CustomParticles,
|
||||||
|
CustomModels_v2,
|
||||||
|
ExtEntityTeleport,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtBitmask {
|
||||||
|
/// gets info about a specific extension
|
||||||
|
fn info(self) -> Option<ExtInfo> {
|
||||||
|
// TODO: add entries as extensions are supported
|
||||||
|
Some(match self {
|
||||||
|
Self::EnvWeatherType => {
|
||||||
|
ExtInfo::new("EnvWeatherType".to_string(), 1, Self::EnvWeatherType)
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// gets info about all extensions
|
||||||
|
pub fn all_contained_info(self) -> Vec<ExtInfo> {
|
||||||
|
[
|
||||||
|
Self::ClickDistance,
|
||||||
|
Self::CustomBlocks,
|
||||||
|
Self::HeldBlock,
|
||||||
|
Self::EmoteFix,
|
||||||
|
Self::TextHotKey,
|
||||||
|
Self::ExtPlayerList,
|
||||||
|
Self::EnvColors,
|
||||||
|
Self::SelectionCuboid,
|
||||||
|
Self::BlockPermissions,
|
||||||
|
Self::ChangeModel,
|
||||||
|
Self::EnvMapAppearance,
|
||||||
|
Self::EnvWeatherType,
|
||||||
|
Self::HackControl,
|
||||||
|
Self::MessageTypes,
|
||||||
|
Self::PlayerClick,
|
||||||
|
Self::LongerMessages,
|
||||||
|
Self::FullCP437,
|
||||||
|
Self::BlockDefinitions,
|
||||||
|
Self::BlockDefinitionsExt,
|
||||||
|
Self::BulkBlockUpdate,
|
||||||
|
Self::TextColors,
|
||||||
|
Self::EnvMapAspect,
|
||||||
|
Self::EntityProperty,
|
||||||
|
Self::ExtEntityPositions,
|
||||||
|
Self::TwoWayPing,
|
||||||
|
Self::InventoryOrder,
|
||||||
|
Self::InstantMOTD,
|
||||||
|
Self::ExtendedBlocks,
|
||||||
|
Self::FastMap,
|
||||||
|
Self::ExtendedTextures,
|
||||||
|
Self::SetHotbar,
|
||||||
|
Self::SetSpawnpoint,
|
||||||
|
Self::VelocityControl,
|
||||||
|
Self::CustomParticles,
|
||||||
|
Self::CustomModels_v2,
|
||||||
|
Self::ExtEntityTeleport,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|flag| (self & flag).info())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use half::f16;
|
use half::f16;
|
||||||
|
|
||||||
use super::{SafeBufExtension, STRING_LENGTH};
|
use super::{client_extended::ExtendedClientPacket, SafeBufExtension, STRING_LENGTH};
|
||||||
|
|
||||||
/// enum for a packet which can be received by the client
|
/// enum for a packet which can be received by the client
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -10,10 +10,10 @@ pub enum ClientPacket {
|
||||||
/// should always be 0x07 for classic clients >= 0.28
|
/// should always be 0x07 for classic clients >= 0.28
|
||||||
protocol_version: u8,
|
protocol_version: u8,
|
||||||
username: String,
|
username: String,
|
||||||
/// currently unverified, original minecraft auth for classic is gone anyway
|
/// used as the password the client sends to the server
|
||||||
/// TODO: use verification key field as password protection? investigate
|
|
||||||
verification_key: String,
|
verification_key: String,
|
||||||
_unused: u8,
|
/// unused in vanilla but used to check the magic number for extension support
|
||||||
|
magic_number: u8,
|
||||||
},
|
},
|
||||||
/// packet sent when a client changes a block
|
/// 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
|
/// because changes are reflected immediately, to restrict changes, server must send back its own SetBlock packet with the original block
|
||||||
|
@ -41,20 +41,12 @@ pub enum ClientPacket {
|
||||||
player_id: i8,
|
player_id: i8,
|
||||||
message: String,
|
message: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// extension packets
|
||||||
|
Extended(ExtendedClientPacket),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientPacket {
|
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,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// gets the size of the packet from the given id (minus one byte for the id)
|
/// gets the size of the packet from the given id (minus one byte for the id)
|
||||||
pub const fn get_size_from_id(id: u8) -> Option<usize> {
|
pub const fn get_size_from_id(id: u8) -> Option<usize> {
|
||||||
Some(match id {
|
Some(match id {
|
||||||
|
@ -62,7 +54,7 @@ impl ClientPacket {
|
||||||
0x05 => 2 + 2 + 2 + 1 + 1,
|
0x05 => 2 + 2 + 2 + 1 + 1,
|
||||||
0x08 => 1 + 2 + 2 + 2 + 1 + 1,
|
0x08 => 1 + 2 + 2 + 2 + 1 + 1,
|
||||||
0x0d => 1 + STRING_LENGTH,
|
0x0d => 1 + STRING_LENGTH,
|
||||||
_ => return None,
|
_ => return ExtendedClientPacket::get_size_from_id(id),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +68,7 @@ impl ClientPacket {
|
||||||
protocol_version: buf.try_get_u8().ok()?,
|
protocol_version: buf.try_get_u8().ok()?,
|
||||||
username: buf.try_get_string().ok()?,
|
username: buf.try_get_string().ok()?,
|
||||||
verification_key: buf.try_get_string().ok()?,
|
verification_key: buf.try_get_string().ok()?,
|
||||||
_unused: buf.try_get_u8().ok()?,
|
magic_number: buf.try_get_u8().ok()?,
|
||||||
},
|
},
|
||||||
0x05 => Self::SetBlock {
|
0x05 => Self::SetBlock {
|
||||||
x: buf.try_get_i16().ok()?,
|
x: buf.try_get_i16().ok()?,
|
||||||
|
@ -97,56 +89,8 @@ impl ClientPacket {
|
||||||
player_id: buf.try_get_i8().ok()?,
|
player_id: buf.try_get_i8().ok()?,
|
||||||
message: buf.try_get_string().ok()?,
|
message: buf.try_get_string().ok()?,
|
||||||
},
|
},
|
||||||
id => {
|
|
||||||
println!("unknown packet id: {id:0x}");
|
id => Self::Extended(ExtendedClientPacket::read(id, buf)?),
|
||||||
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)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
42
src/packet/client_extended.rs
Normal file
42
src/packet/client_extended.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use super::{SafeBufExtension, STRING_LENGTH};
|
||||||
|
|
||||||
|
/// extended client packets
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ExtendedClientPacket {
|
||||||
|
/// packet containing the client name and the number of extensions it supports
|
||||||
|
ExtInfo {
|
||||||
|
app_name: String,
|
||||||
|
extension_count: i16,
|
||||||
|
},
|
||||||
|
/// packet containing a supported extension name and version
|
||||||
|
ExtEntry { ext_name: String, version: i32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtendedClientPacket {
|
||||||
|
/// gets the size of the packet from the given id (minus one byte for the id)
|
||||||
|
pub const fn get_size_from_id(id: u8) -> Option<usize> {
|
||||||
|
Some(match id {
|
||||||
|
0x10 => STRING_LENGTH + 2,
|
||||||
|
0x11 => STRING_LENGTH + 4,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reads the packet
|
||||||
|
pub fn read<B>(id: u8, buf: &mut B) -> Option<Self>
|
||||||
|
where
|
||||||
|
B: SafeBufExtension,
|
||||||
|
{
|
||||||
|
Some(match id {
|
||||||
|
0x10 => Self::ExtInfo {
|
||||||
|
app_name: buf.try_get_string().ok()?,
|
||||||
|
extension_count: buf.try_get_i16().ok()?,
|
||||||
|
},
|
||||||
|
0x11 => Self::ExtEntry {
|
||||||
|
ext_name: buf.try_get_string().ok()?,
|
||||||
|
version: buf.try_get_i32().ok()?,
|
||||||
|
},
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
use half::f16;
|
use half::f16;
|
||||||
|
|
||||||
use crate::player::PlayerType;
|
use crate::{level::WeatherType, player::PlayerType, SERVER_NAME};
|
||||||
|
|
||||||
|
use super::ExtBitmask;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -91,6 +93,14 @@ pub enum ServerPacket {
|
||||||
/// 0x00 for normal, 0x64 for op
|
/// 0x00 for normal, 0x64 for op
|
||||||
user_type: PlayerType,
|
user_type: PlayerType,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// extension packets
|
||||||
|
/// packet to send info about the server's extensions
|
||||||
|
ExtInfo {},
|
||||||
|
/// packet to send info about an extension on the server
|
||||||
|
ExtEntry { ext_name: String, version: i32 },
|
||||||
|
/// informs the client that it should update the current weather
|
||||||
|
EnvWeatherType { weather_type: WeatherType },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerPacket {
|
impl ServerPacket {
|
||||||
|
@ -112,6 +122,10 @@ impl ServerPacket {
|
||||||
Self::Message { .. } => 0x0d,
|
Self::Message { .. } => 0x0d,
|
||||||
Self::DisconnectPlayer { .. } => 0x0e,
|
Self::DisconnectPlayer { .. } => 0x0e,
|
||||||
Self::UpdateUserType { .. } => 0x0f,
|
Self::UpdateUserType { .. } => 0x0f,
|
||||||
|
|
||||||
|
Self::ExtInfo {} => 0x10,
|
||||||
|
Self::ExtEntry { .. } => 0x11,
|
||||||
|
Self::EnvWeatherType { .. } => 0x1f,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +235,14 @@ impl ServerPacket {
|
||||||
}
|
}
|
||||||
Self::DisconnectPlayer { disconnect_reason } => writer.write_string(disconnect_reason),
|
Self::DisconnectPlayer { disconnect_reason } => writer.write_string(disconnect_reason),
|
||||||
Self::UpdateUserType { user_type } => writer.write_u8(user_type.into()),
|
Self::UpdateUserType { user_type } => writer.write_u8(user_type.into()),
|
||||||
|
|
||||||
|
Self::ExtInfo {} => writer
|
||||||
|
.write_string(SERVER_NAME)
|
||||||
|
.write_i16(ExtBitmask::all().all_contained_info().len() as i16),
|
||||||
|
Self::ExtEntry { ext_name, version } => {
|
||||||
|
writer.write_string(ext_name).write_i32(*version)
|
||||||
|
}
|
||||||
|
Self::EnvWeatherType { weather_type } => writer.write_u8(weather_type.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::net::SocketAddr;
|
||||||
use half::f16;
|
use half::f16;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::packet::server::ServerPacket;
|
use crate::packet::{server::ServerPacket, ExtBitmask};
|
||||||
|
|
||||||
/// struct for players
|
/// struct for players
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -27,6 +27,8 @@ pub struct Player {
|
||||||
|
|
||||||
/// the player's IP address
|
/// the player's IP address
|
||||||
pub _addr: SocketAddr,
|
pub _addr: SocketAddr,
|
||||||
|
/// the player's supported extensions
|
||||||
|
pub extensions: ExtBitmask,
|
||||||
/// queue of packets to be sent to this player
|
/// queue of packets to be sent to this player
|
||||||
pub packets_to_send: Vec<ServerPacket>,
|
pub packets_to_send: Vec<ServerPacket>,
|
||||||
/// whether this player should be kicked and the message to give
|
/// whether this player should be kicked and the message to give
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::{
|
||||||
use self::config::ServerConfig;
|
use self::config::ServerConfig;
|
||||||
|
|
||||||
const TICK_DURATION: std::time::Duration = std::time::Duration::from_millis(50);
|
const TICK_DURATION: std::time::Duration = std::time::Duration::from_millis(50);
|
||||||
const LEVEL_PATH: &str = "level.clw";
|
const LEVELS_PATH: &str = "levels";
|
||||||
|
|
||||||
/// the server
|
/// the server
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -59,9 +59,13 @@ impl ServerData {
|
||||||
impl Server {
|
impl Server {
|
||||||
/// creates a new server with a generated level
|
/// creates a new server with a generated level
|
||||||
pub async fn new(config: ServerConfig) -> std::io::Result<Self> {
|
pub async fn new(config: ServerConfig) -> std::io::Result<Self> {
|
||||||
let level_path = PathBuf::from(LEVEL_PATH);
|
let levels_path = PathBuf::from(LEVELS_PATH);
|
||||||
|
if !levels_path.exists() {
|
||||||
|
std::fs::create_dir_all(&levels_path)?;
|
||||||
|
}
|
||||||
|
let level_path = levels_path.join(&config.level_name);
|
||||||
let level = if level_path.exists() {
|
let level = if level_path.exists() {
|
||||||
Level::load(level_path).await
|
Level::load(level_path).await?
|
||||||
} else {
|
} else {
|
||||||
println!("generating level");
|
println!("generating level");
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
@ -71,6 +75,7 @@ impl Server {
|
||||||
config.level_size.z,
|
config.level_size.z,
|
||||||
);
|
);
|
||||||
config.generation.generate(&mut level, &mut rng);
|
config.generation.generate(&mut level, &mut rng);
|
||||||
|
level.save(level_path).await?;
|
||||||
println!("done!");
|
println!("done!");
|
||||||
level
|
level
|
||||||
};
|
};
|
||||||
|
@ -104,9 +109,7 @@ impl Server {
|
||||||
println!("connection from {addr}");
|
println!("connection from {addr}");
|
||||||
let data = data.clone();
|
let data = data.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
network::handle_stream(stream, addr, data)
|
network::handle_stream(stream, addr, data).await;
|
||||||
.await
|
|
||||||
.expect("failed to handle client stream");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -116,7 +119,10 @@ impl Server {
|
||||||
// TODO: cancel pending tasks/send out "Server is stopping" messages *here* instead of elsewhere
|
// TODO: cancel pending tasks/send out "Server is stopping" messages *here* instead of elsewhere
|
||||||
// rn the message isn't guaranteed to actually go out........
|
// rn the message isn't guaranteed to actually go out........
|
||||||
|
|
||||||
self.data.read().await.level.save(LEVEL_PATH).await;
|
let data = self.data.read().await;
|
||||||
|
data.level
|
||||||
|
.save(PathBuf::from(LEVELS_PATH).join(&data.config.level_name))
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -154,7 +160,10 @@ async fn handle_ticks(data: Arc<RwLock<ServerData>>) {
|
||||||
if data.config.auto_save_minutes != 0
|
if data.config.auto_save_minutes != 0
|
||||||
&& last_auto_save.elapsed().as_secs() / 60 >= data.config.auto_save_minutes
|
&& last_auto_save.elapsed().as_secs() / 60 >= data.config.auto_save_minutes
|
||||||
{
|
{
|
||||||
data.level.save(LEVEL_PATH).await;
|
data.level
|
||||||
|
.save(PathBuf::from(LEVELS_PATH).join(&data.config.level_name))
|
||||||
|
.await
|
||||||
|
.expect("failed to autosave level");
|
||||||
last_auto_save = std::time::Instant::now();
|
last_auto_save = std::time::Instant::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ pub struct ServerConfig {
|
||||||
pub protection_mode: ServerProtectionMode,
|
pub protection_mode: ServerProtectionMode,
|
||||||
/// map of user permissions
|
/// map of user permissions
|
||||||
pub player_perms: BTreeMap<String, PlayerType>,
|
pub player_perms: BTreeMap<String, PlayerType>,
|
||||||
|
/// the level's name
|
||||||
|
pub level_name: String,
|
||||||
/// the level's size
|
/// the level's size
|
||||||
pub level_size: ConfigCoordinates,
|
pub level_size: ConfigCoordinates,
|
||||||
/// the level's spawn point
|
/// the level's spawn point
|
||||||
|
@ -42,6 +44,7 @@ impl Default for ServerConfig {
|
||||||
motd: "here's the default server motd".to_string(),
|
motd: "here's the default server motd".to_string(),
|
||||||
protection_mode: ServerProtectionMode::None,
|
protection_mode: ServerProtectionMode::None,
|
||||||
player_perms: Default::default(),
|
player_perms: Default::default(),
|
||||||
|
level_name: "default".to_string(),
|
||||||
level_size: ConfigCoordinates {
|
level_size: ConfigCoordinates {
|
||||||
x: 256,
|
x: 256,
|
||||||
y: 64,
|
y: 64,
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use std::{collections::VecDeque, io::Write, net::SocketAddr, sync::Arc};
|
mod extensions;
|
||||||
|
|
||||||
|
use std::{io::Write, net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use flate2::{write::GzEncoder, Compression};
|
use flate2::{write::GzEncoder, Compression};
|
||||||
use half::f16;
|
use half::f16;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt, Interest},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
net::TcpStream,
|
net::TcpStream,
|
||||||
sync::RwLock,
|
sync::RwLock,
|
||||||
};
|
};
|
||||||
|
@ -12,21 +14,50 @@ use tokio::{
|
||||||
use crate::{
|
use crate::{
|
||||||
command::Command,
|
command::Command,
|
||||||
level::{block::BLOCK_INFO, BlockUpdate, Level},
|
level::{block::BLOCK_INFO, BlockUpdate, Level},
|
||||||
packet::{client::ClientPacket, server::ServerPacket, PacketWriter, ARRAY_LENGTH},
|
packet::{
|
||||||
|
client::ClientPacket, server::ServerPacket, ExtBitmask, PacketWriter, ARRAY_LENGTH,
|
||||||
|
EXTENSION_MAGIC_NUMBER,
|
||||||
|
},
|
||||||
player::{Player, PlayerType},
|
player::{Player, PlayerType},
|
||||||
server::config::ServerProtectionMode,
|
server::config::ServerProtectionMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::ServerData;
|
use super::ServerData;
|
||||||
|
|
||||||
|
async fn next_packet(stream: &mut TcpStream) -> std::io::Result<Option<ClientPacket>> {
|
||||||
|
let id = stream.read_u8().await?;
|
||||||
|
|
||||||
|
if let Some(size) = ClientPacket::get_size_from_id(id) {
|
||||||
|
let mut buf = BytesMut::zeroed(size);
|
||||||
|
stream.read_exact(&mut buf).await?;
|
||||||
|
Ok(ClientPacket::read(id, &mut buf))
|
||||||
|
} else {
|
||||||
|
println!("unknown packet id: {id:0x}");
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_packets<I>(stream: &mut TcpStream, packets: I) -> std::io::Result<()>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = ServerPacket>,
|
||||||
|
{
|
||||||
|
for packet in packets {
|
||||||
|
let writer = PacketWriter::default().write_u8(packet.get_id());
|
||||||
|
let msg = packet.write(writer).into_raw_packet();
|
||||||
|
stream.write_all(&msg).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) async fn handle_stream(
|
pub(super) async fn handle_stream(
|
||||||
mut stream: TcpStream,
|
mut stream: TcpStream,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
data: Arc<RwLock<ServerData>>,
|
data: Arc<RwLock<ServerData>>,
|
||||||
) -> std::io::Result<()> {
|
) {
|
||||||
let mut own_id: i8 = -1;
|
let mut own_id: i8 = -1;
|
||||||
let r = handle_stream_inner(&mut stream, addr, data.clone(), &mut own_id).await;
|
let r = handle_stream_inner(&mut stream, addr, data.clone(), &mut own_id).await;
|
||||||
|
|
||||||
|
println!("{addr} is no longer connected");
|
||||||
match r {
|
match r {
|
||||||
Ok(disconnect_reason) => {
|
Ok(disconnect_reason) => {
|
||||||
if let Some(disconnect_reason) = disconnect_reason {
|
if let Some(disconnect_reason) = disconnect_reason {
|
||||||
|
@ -38,7 +69,12 @@ pub(super) async fn handle_stream(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("Error in stream handler for <{addr}>: {e}"),
|
Err(e) => {
|
||||||
|
// unexpected eof is expected when clients disconnect
|
||||||
|
if e.kind() != std::io::ErrorKind::UnexpectedEof {
|
||||||
|
eprintln!("Error in stream handler for <{addr}>: {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = stream.shutdown().await {
|
if let Err(e) = stream.shutdown().await {
|
||||||
|
@ -60,8 +96,6 @@ pub(super) async fn handle_stream(
|
||||||
player.packets_to_send.push(message_packet.clone());
|
player.packets_to_send.push(message_packet.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_stream_inner(
|
async fn handle_stream_inner(
|
||||||
|
@ -70,13 +104,11 @@ async fn handle_stream_inner(
|
||||||
data: Arc<RwLock<ServerData>>,
|
data: Arc<RwLock<ServerData>>,
|
||||||
own_id: &mut i8,
|
own_id: &mut i8,
|
||||||
) -> std::io::Result<Option<String>> {
|
) -> std::io::Result<Option<String>> {
|
||||||
let mut reply_queue: VecDeque<ServerPacket> = VecDeque::new();
|
let mut reply_queue: Vec<ServerPacket> = Vec::new();
|
||||||
let mut read_buf;
|
|
||||||
let mut id_buf;
|
|
||||||
|
|
||||||
macro_rules! msg {
|
macro_rules! msg {
|
||||||
($message:expr) => {
|
($message:expr) => {
|
||||||
reply_queue.push_back(ServerPacket::Message {
|
reply_queue.push(ServerPacket::Message {
|
||||||
player_id: -1,
|
player_id: -1,
|
||||||
message: $message,
|
message: $message,
|
||||||
});
|
});
|
||||||
|
@ -90,33 +122,13 @@ async fn handle_stream_inner(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ready = stream
|
if let Some(packet) = next_packet(stream).await? {
|
||||||
.ready(Interest::READABLE | Interest::WRITABLE)
|
match packet {
|
||||||
.await?;
|
|
||||||
|
|
||||||
if ready.is_read_closed() {
|
|
||||||
println!("disconnecting {addr}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ready.is_readable() {
|
|
||||||
id_buf = [0u8];
|
|
||||||
match stream.try_read(&mut id_buf) {
|
|
||||||
Ok(n) => {
|
|
||||||
if n == 1 {
|
|
||||||
if let Some(size) = ClientPacket::get_size_from_id(id_buf[0]) {
|
|
||||||
read_buf = BytesMut::zeroed(size);
|
|
||||||
|
|
||||||
stream.read_exact(&mut read_buf).await?;
|
|
||||||
|
|
||||||
match ClientPacket::read(id_buf[0], &mut read_buf)
|
|
||||||
.expect("should never fail: id already checked")
|
|
||||||
{
|
|
||||||
ClientPacket::PlayerIdentification {
|
ClientPacket::PlayerIdentification {
|
||||||
protocol_version,
|
protocol_version,
|
||||||
username,
|
username,
|
||||||
verification_key,
|
verification_key,
|
||||||
_unused,
|
magic_number,
|
||||||
} => {
|
} => {
|
||||||
if protocol_version != 0x07 {
|
if protocol_version != 0x07 {
|
||||||
return Ok(Some("Unknown protocol version! Please connect with a classic 0.30-compatible client.".to_string()));
|
return Ok(Some("Unknown protocol version! Please connect with a classic 0.30-compatible client.".to_string()));
|
||||||
|
@ -146,10 +158,7 @@ async fn handle_stream_inner(
|
||||||
|
|
||||||
for player in &data.players {
|
for player in &data.players {
|
||||||
if player.username == username {
|
if player.username == username {
|
||||||
return Ok(Some(
|
return Ok(Some("Player with username already connected!".to_string()));
|
||||||
"Player with username already connected!"
|
|
||||||
.to_string(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,11 +184,16 @@ async fn handle_stream_inner(
|
||||||
yaw: 0,
|
yaw: 0,
|
||||||
pitch: 0,
|
pitch: 0,
|
||||||
permissions: player_type,
|
permissions: player_type,
|
||||||
|
extensions: ExtBitmask::none(),
|
||||||
packets_to_send: Vec::new(),
|
packets_to_send: Vec::new(),
|
||||||
should_be_kicked: None,
|
should_be_kicked: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
reply_queue.push_back(ServerPacket::ServerIdentification {
|
if magic_number == EXTENSION_MAGIC_NUMBER {
|
||||||
|
player.extensions = extensions::get_supported_extensions(stream).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply_queue.push(ServerPacket::ServerIdentification {
|
||||||
protocol_version: 0x07,
|
protocol_version: 0x07,
|
||||||
server_name: data.config.name.clone(),
|
server_name: data.config.name.clone(),
|
||||||
server_motd: data.config.motd.clone(),
|
server_motd: data.config.motd.clone(),
|
||||||
|
@ -187,8 +201,13 @@ async fn handle_stream_inner(
|
||||||
});
|
});
|
||||||
|
|
||||||
println!("generating level packets");
|
println!("generating level packets");
|
||||||
reply_queue
|
reply_queue.extend(build_level_packets(&data.level).into_iter());
|
||||||
.extend(build_level_packets(&data.level).into_iter());
|
|
||||||
|
if player.extensions.contains(ExtBitmask::EnvWeatherType) {
|
||||||
|
reply_queue.push(ServerPacket::EnvWeatherType {
|
||||||
|
weather_type: data.level.weather,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let username = player.username.clone();
|
let username = player.username.clone();
|
||||||
|
|
||||||
|
@ -228,7 +247,7 @@ async fn handle_stream_inner(
|
||||||
for player in &mut data.players {
|
for player in &mut data.players {
|
||||||
player.packets_to_send.push(spawn_packet.clone());
|
player.packets_to_send.push(spawn_packet.clone());
|
||||||
if player.id != *own_id {
|
if player.id != *own_id {
|
||||||
reply_queue.push_back(ServerPacket::SpawnPlayer {
|
reply_queue.push(ServerPacket::SpawnPlayer {
|
||||||
player_id: player.id,
|
player_id: player.id,
|
||||||
player_name: player.username.clone(),
|
player_name: player.username.clone(),
|
||||||
x: player.x,
|
x: player.x,
|
||||||
|
@ -241,7 +260,7 @@ async fn handle_stream_inner(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
msg!("&dWelcome to the server! Enjoyyyyyy".to_string());
|
msg!("&dWelcome to the server! Enjoyyyyyy".to_string());
|
||||||
reply_queue.push_back(ServerPacket::UpdateUserType {
|
reply_queue.push(ServerPacket::UpdateUserType {
|
||||||
user_type: PlayerType::Operator,
|
user_type: PlayerType::Operator,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -260,9 +279,7 @@ async fn handle_stream_inner(
|
||||||
|| y.clamp(0, data.level.y_size as i16 - 1) != y
|
|| y.clamp(0, data.level.y_size as i16 - 1) != y
|
||||||
|| z.clamp(0, data.level.z_size as i16 - 1) != z
|
|| z.clamp(0, data.level.z_size as i16 - 1) != z
|
||||||
{
|
{
|
||||||
return Ok(Some(
|
return Ok(Some("Attempt to place block out of bounds".to_string()));
|
||||||
"Attempt to place block out of bounds".to_string(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_block_info = BLOCK_INFO.get(&block_type);
|
let new_block_info = BLOCK_INFO.get(&block_type);
|
||||||
|
@ -272,8 +289,7 @@ async fn handle_stream_inner(
|
||||||
}
|
}
|
||||||
let new_block_info = new_block_info.expect("will never fail");
|
let new_block_info = new_block_info.expect("will never fail");
|
||||||
let mut cancel = false;
|
let mut cancel = false;
|
||||||
let block =
|
let block = data.level.get_block(x as usize, y as usize, z as usize);
|
||||||
data.level.get_block(x as usize, y as usize, z as usize);
|
|
||||||
let block_info = BLOCK_INFO
|
let block_info = BLOCK_INFO
|
||||||
.get(&block)
|
.get(&block)
|
||||||
.expect("missing block information for block!");
|
.expect("missing block information for block!");
|
||||||
|
@ -293,7 +309,7 @@ async fn handle_stream_inner(
|
||||||
}
|
}
|
||||||
|
|
||||||
if cancel {
|
if cancel {
|
||||||
reply_queue.push_back(ServerPacket::SetBlock {
|
reply_queue.push(ServerPacket::SetBlock {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
z,
|
z,
|
||||||
|
@ -365,25 +381,27 @@ async fn handle_stream_inner(
|
||||||
.expect("should never fail")
|
.expect("should never fail")
|
||||||
.username
|
.username
|
||||||
);
|
);
|
||||||
data.spread_packet(ServerPacket::Message {
|
data.spread_packet(ServerPacket::Message { player_id, message });
|
||||||
player_id,
|
}
|
||||||
message,
|
}
|
||||||
});
|
|
||||||
}
|
ClientPacket::Extended(_packet) => {
|
||||||
}
|
// extended packets!
|
||||||
}
|
return Ok(Some(
|
||||||
} else {
|
"Unexpected extension packet in this phase!".to_string(),
|
||||||
println!("unknown packet id: {}", id_buf[0]);
|
));
|
||||||
}
|
// match packet {
|
||||||
}
|
// packet => {
|
||||||
}
|
// println!("improper client packet for this phase!: {packet:#?}");
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
|
// return Ok(Some(
|
||||||
Err(e) => return Err(e),
|
// "Client sent invalid packet for this phase".to_string(),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ready.is_writable() {
|
|
||||||
{
|
|
||||||
let mut data = data.write().await;
|
let mut data = data.write().await;
|
||||||
if let Some(player) = data.players.iter_mut().find(|p| p.id == *own_id) {
|
if let Some(player) = data.players.iter_mut().find(|p| p.id == *own_id) {
|
||||||
for mut packet in player.packets_to_send.drain(..) {
|
for mut packet in player.packets_to_send.drain(..) {
|
||||||
|
@ -395,23 +413,13 @@ async fn handle_stream_inner(
|
||||||
packet.set_player_id(-1);
|
packet.set_player_id(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reply_queue.push_back(packet);
|
reply_queue.push(packet);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(packet) = reply_queue.pop_front() {
|
write_packets(stream, reply_queue.drain(..)).await?;
|
||||||
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
|
/// 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> {
|
fn build_level_packets(level: &Level) -> Vec<ServerPacket> {
|
||||||
|
|
65
src/server/network/extensions.rs
Normal file
65
src/server/network/extensions.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use crate::packet::{
|
||||||
|
client::ClientPacket, client_extended::ExtendedClientPacket, server::ServerPacket, ExtBitmask,
|
||||||
|
ExtInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{next_packet, write_packets};
|
||||||
|
|
||||||
|
pub async fn get_supported_extensions(stream: &mut TcpStream) -> std::io::Result<ExtBitmask> {
|
||||||
|
let extensions = ExtBitmask::all().all_contained_info();
|
||||||
|
|
||||||
|
write_packets(
|
||||||
|
stream,
|
||||||
|
Some(ServerPacket::ExtInfo {})
|
||||||
|
.into_iter()
|
||||||
|
.chain(extensions.iter().map(|info| ServerPacket::ExtEntry {
|
||||||
|
ext_name: info.ext_name.to_string(),
|
||||||
|
version: info.version,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let client_extensions = if let Some(ClientPacket::Extended(ExtendedClientPacket::ExtInfo {
|
||||||
|
app_name,
|
||||||
|
extension_count,
|
||||||
|
})) = next_packet(stream).await?
|
||||||
|
{
|
||||||
|
println!("client name: {app_name}");
|
||||||
|
let mut client_extensions = Vec::with_capacity(extension_count as usize);
|
||||||
|
for _ in 0..extension_count {
|
||||||
|
if let Some(ClientPacket::Extended(ExtendedClientPacket::ExtEntry {
|
||||||
|
ext_name,
|
||||||
|
version,
|
||||||
|
})) = next_packet(stream).await?
|
||||||
|
{
|
||||||
|
client_extensions.push(ExtInfo::new(ext_name, version, ExtBitmask::none()));
|
||||||
|
} else {
|
||||||
|
panic!("expected ExtEntry packet!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client_extensions.retain_mut(|cext| {
|
||||||
|
if let Some(sext) = extensions
|
||||||
|
.iter()
|
||||||
|
.find(|sext| sext.ext_name == cext.ext_name && sext.version == cext.version)
|
||||||
|
{
|
||||||
|
cext.bitmask = sext.bitmask;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client_extensions
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("mutual extensions: {client_extensions:?}");
|
||||||
|
|
||||||
|
let final_bitmask = client_extensions
|
||||||
|
.into_iter()
|
||||||
|
.fold(ExtBitmask::none(), |acc, ext| acc | ext.bitmask);
|
||||||
|
|
||||||
|
Ok(final_bitmask)
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue