mirror of
https://github.com/zyllian/classics.git
synced 2025-01-18 03:32:41 -08:00
This commit is contained in:
parent
d53f37d664
commit
be81b8d581
11 changed files with 199 additions and 45 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -126,6 +126,7 @@ dependencies = [
|
|||
"safer-bytes",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
@ -191,6 +192,12 @@ dependencies = [
|
|||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
|
@ -424,6 +431,12 @@ version = "0.1.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.17"
|
||||
|
@ -503,6 +516,28 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
|
|
|
@ -17,4 +17,5 @@ rand = "0.8"
|
|||
safer-bytes = "0.2"
|
||||
serde = {version = "1", features = ["derive"]}
|
||||
serde_json = "1"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
tokio = {version = "1", features = ["full"]}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::{
|
||||
packet::{server::ServerPacket, STRING_LENGTH},
|
||||
packet::{server::ServerPacket, ExtBitmask, STRING_LENGTH},
|
||||
player::PlayerType,
|
||||
server::{
|
||||
config::{ConfigCoordinatesWithOrientation, ServerProtectionMode},
|
||||
network::set_player_inventory,
|
||||
ServerData,
|
||||
},
|
||||
};
|
||||
|
@ -87,7 +88,10 @@ impl<'m> Command<'m> {
|
|||
CMD_SAY => Self::Say { message: arguments },
|
||||
CMD_SETPERM => Self::SetPermissions {
|
||||
player_username: Self::next_string(&mut arguments)?,
|
||||
permissions: arguments.trim().try_into()?,
|
||||
permissions: arguments
|
||||
.trim()
|
||||
.try_into()
|
||||
.map_err(|_| format!("&cUnknown permissions type: {arguments}"))?,
|
||||
},
|
||||
CMD_KICK => {
|
||||
let username = Self::next_string(&mut arguments)?;
|
||||
|
@ -297,7 +301,7 @@ impl<'m> Command<'m> {
|
|||
return messages;
|
||||
}
|
||||
|
||||
let perm_string = serde_json::to_string(&permissions).expect("should never fail");
|
||||
let perm_string: &'static str = permissions.into();
|
||||
|
||||
if let Some(current) = data.config.player_perms.get(player_username) {
|
||||
if *current >= player_perms {
|
||||
|
@ -329,6 +333,15 @@ impl<'m> Command<'m> {
|
|||
player_id: p.id,
|
||||
message: format!("Your permissions have been set to {perm_string}"),
|
||||
});
|
||||
|
||||
if p.extensions.contains(ExtBitmask::InventoryOrder) {
|
||||
set_player_inventory(
|
||||
p.permissions,
|
||||
p.extensions,
|
||||
p.custom_blocks_support_level,
|
||||
&mut p.packets_to_send,
|
||||
);
|
||||
}
|
||||
}
|
||||
messages.push(format!(
|
||||
"Set permissions for {player_username} to {perm_string}"
|
||||
|
|
|
@ -4,6 +4,9 @@ use internment::Intern;
|
|||
|
||||
use crate::player::PlayerType;
|
||||
|
||||
/// the level of custom blocks supported by the server
|
||||
pub const CUSTOM_BLOCKS_SUPPORT_LEVEL: u8 = 1;
|
||||
|
||||
/// information about all blocks implemented
|
||||
pub static BLOCK_INFO: LazyLock<BTreeMap<u8, BlockInfo>> = LazyLock::new(|| {
|
||||
[
|
||||
|
@ -19,7 +22,7 @@ pub static BLOCK_INFO: LazyLock<BTreeMap<u8, BlockInfo>> = LazyLock::new(|| {
|
|||
),
|
||||
(
|
||||
0x07,
|
||||
BlockInfo::new("bedrock").perm(PlayerType::Operator, PlayerType::Operator),
|
||||
BlockInfo::new("bedrock").perm(PlayerType::Moderator, PlayerType::Moderator),
|
||||
),
|
||||
(
|
||||
0x08,
|
||||
|
@ -28,13 +31,13 @@ pub static BLOCK_INFO: LazyLock<BTreeMap<u8, BlockInfo>> = LazyLock::new(|| {
|
|||
stationary: 0x09,
|
||||
ticks_to_spread: 3,
|
||||
})
|
||||
.perm(PlayerType::Operator, PlayerType::Normal),
|
||||
.perm(PlayerType::Moderator, PlayerType::Normal),
|
||||
),
|
||||
(
|
||||
0x09,
|
||||
BlockInfo::new("water_stationary")
|
||||
.block_type(BlockType::FluidStationary { moving: 0x08 })
|
||||
.perm(PlayerType::Operator, PlayerType::Normal),
|
||||
.perm(PlayerType::Moderator, PlayerType::Normal),
|
||||
),
|
||||
(
|
||||
0x0a,
|
||||
|
@ -43,13 +46,13 @@ pub static BLOCK_INFO: LazyLock<BTreeMap<u8, BlockInfo>> = LazyLock::new(|| {
|
|||
stationary: 0x0b,
|
||||
ticks_to_spread: 15,
|
||||
})
|
||||
.perm(PlayerType::Operator, PlayerType::Normal),
|
||||
.perm(PlayerType::Moderator, PlayerType::Normal),
|
||||
),
|
||||
(
|
||||
0x0b,
|
||||
BlockInfo::new("lava_stationary")
|
||||
.block_type(BlockType::FluidStationary { moving: 0x0a })
|
||||
.perm(PlayerType::Operator, PlayerType::Normal),
|
||||
.perm(PlayerType::Moderator, PlayerType::Normal),
|
||||
),
|
||||
(0x0c, BlockInfo::new("sand")),
|
||||
(0x0d, BlockInfo::new("gravel")),
|
||||
|
|
|
@ -205,6 +205,9 @@ impl ExtBitmask {
|
|||
Self::EnvWeatherType => {
|
||||
ExtInfo::new("EnvWeatherType".to_string(), 1, Self::EnvWeatherType)
|
||||
}
|
||||
Self::InventoryOrder => {
|
||||
ExtInfo::new("InventoryOrder".to_string(), 1, Self::InventoryOrder)
|
||||
}
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ pub enum ExtendedClientPacket {
|
|||
},
|
||||
/// packet containing a supported extension name and version
|
||||
ExtEntry { ext_name: String, version: i32 },
|
||||
/// packet containing the support level for custom blocks from the client
|
||||
CustomBlockSupportLevel { support_level: u8 },
|
||||
}
|
||||
|
||||
impl ExtendedClientPacket {
|
||||
|
@ -18,6 +20,7 @@ impl ExtendedClientPacket {
|
|||
Some(match id {
|
||||
0x10 => STRING_LENGTH + 2,
|
||||
0x11 => STRING_LENGTH + 4,
|
||||
0x13 => 1,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
@ -36,6 +39,9 @@ impl ExtendedClientPacket {
|
|||
ext_name: buf.try_get_string().ok()?,
|
||||
version: buf.try_get_i32().ok()?,
|
||||
},
|
||||
0x13 => Self::CustomBlockSupportLevel {
|
||||
support_level: buf.try_get_u8().ok()?,
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
use half::f16;
|
||||
|
||||
use crate::{level::WeatherType, player::PlayerType, SERVER_NAME};
|
||||
use crate::{
|
||||
level::{block::CUSTOM_BLOCKS_SUPPORT_LEVEL, WeatherType},
|
||||
player::PlayerType,
|
||||
SERVER_NAME,
|
||||
};
|
||||
|
||||
use super::ExtBitmask;
|
||||
|
||||
|
@ -17,9 +21,9 @@ pub enum ServerPacket {
|
|||
},
|
||||
/// 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 {},
|
||||
Ping,
|
||||
/// informs clients that there is incoming level data
|
||||
LevelInitialize {},
|
||||
LevelInitialize,
|
||||
/// packet to send a chunk (not minecraft chunk) of gzipped level data
|
||||
LevelDataChunk {
|
||||
chunk_length: i16,
|
||||
|
@ -96,13 +100,17 @@ pub enum ServerPacket {
|
|||
|
||||
// extension packets
|
||||
/// packet to send info about the server's extensions
|
||||
ExtInfo {},
|
||||
ExtInfo,
|
||||
/// packet to send info about an extension on the server
|
||||
ExtEntry { ext_name: String, version: i32 },
|
||||
/// packet to send the server's supported custom blocks
|
||||
CustomBlockSupportLevel,
|
||||
/// packet to set a player's currently held block
|
||||
HoldThis { block: u8, prevent_change: bool },
|
||||
/// informs the client that it should update the current weather
|
||||
EnvWeatherType { weather_type: WeatherType },
|
||||
/// packet to set a block's position in the client's inventory
|
||||
SetInventoryOrder { order: u8, block: u8 },
|
||||
}
|
||||
|
||||
impl ServerPacket {
|
||||
|
@ -110,8 +118,8 @@ impl ServerPacket {
|
|||
pub fn get_id(&self) -> u8 {
|
||||
match self {
|
||||
Self::ServerIdentification { .. } => 0x00,
|
||||
Self::Ping {} => 0x01,
|
||||
Self::LevelInitialize {} => 0x02,
|
||||
Self::Ping => 0x01,
|
||||
Self::LevelInitialize => 0x02,
|
||||
Self::LevelDataChunk { .. } => 0x03,
|
||||
Self::LevelFinalize { .. } => 0x04,
|
||||
Self::SetBlock { .. } => 0x06,
|
||||
|
@ -125,10 +133,12 @@ impl ServerPacket {
|
|||
Self::DisconnectPlayer { .. } => 0x0e,
|
||||
Self::UpdateUserType { .. } => 0x0f,
|
||||
|
||||
Self::ExtInfo {} => 0x10,
|
||||
Self::ExtInfo => 0x10,
|
||||
Self::ExtEntry { .. } => 0x11,
|
||||
Self::CustomBlockSupportLevel { .. } => 0x13,
|
||||
Self::HoldThis { .. } => 0x14,
|
||||
Self::EnvWeatherType { .. } => 0x1f,
|
||||
Self::SetInventoryOrder { .. } => 0x2c,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,8 +155,8 @@ impl ServerPacket {
|
|||
.write_string(server_name)
|
||||
.write_string(server_motd)
|
||||
.write_u8(user_type.into()),
|
||||
Self::Ping {} => writer,
|
||||
Self::LevelInitialize {} => writer,
|
||||
Self::Ping => writer,
|
||||
Self::LevelInitialize => writer,
|
||||
Self::LevelDataChunk {
|
||||
chunk_length,
|
||||
chunk_data,
|
||||
|
@ -239,17 +249,19 @@ impl ServerPacket {
|
|||
Self::DisconnectPlayer { disconnect_reason } => writer.write_string(disconnect_reason),
|
||||
Self::UpdateUserType { user_type } => writer.write_u8(user_type.into()),
|
||||
|
||||
Self::ExtInfo {} => writer
|
||||
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::CustomBlockSupportLevel => writer.write_u8(CUSTOM_BLOCKS_SUPPORT_LEVEL),
|
||||
Self::HoldThis {
|
||||
block,
|
||||
prevent_change,
|
||||
} => writer.write_u8(*block).write_bool(*prevent_change),
|
||||
Self::EnvWeatherType { weather_type } => writer.write_u8(weather_type.into()),
|
||||
Self::SetInventoryOrder { order, block } => writer.write_u8(*order).write_u8(*block),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ pub struct Player {
|
|||
pub _addr: SocketAddr,
|
||||
/// the player's supported extensions
|
||||
pub extensions: ExtBitmask,
|
||||
/// the level of custom blocks this client supports
|
||||
pub custom_blocks_support_level: u8,
|
||||
/// queue of packets to be sent to this player
|
||||
pub packets_to_send: Vec<ServerPacket>,
|
||||
/// whether this player should be kicked and the message to give
|
||||
|
@ -36,7 +38,20 @@ pub struct Player {
|
|||
}
|
||||
|
||||
/// enum describing types of players
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
strum::EnumString,
|
||||
strum::IntoStaticStr,
|
||||
)]
|
||||
#[strum(ascii_case_insensitive)]
|
||||
pub enum PlayerType {
|
||||
/// a normal player
|
||||
Normal,
|
||||
|
@ -61,16 +76,3 @@ impl From<&PlayerType> for u8 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for PlayerType {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
Ok(match value.to_lowercase().as_str() {
|
||||
"normal" => Self::Normal,
|
||||
"moderator" => Self::Moderator,
|
||||
"operator" => Self::Operator,
|
||||
value => return Err(format!("Unknown permissions type: {value}")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
pub mod config;
|
||||
mod network;
|
||||
pub(crate) mod network;
|
||||
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ use crate::{
|
|||
client::ClientPacket, server::ServerPacket, ExtBitmask, PacketWriter, ARRAY_LENGTH,
|
||||
EXTENSION_MAGIC_NUMBER,
|
||||
},
|
||||
player::Player,
|
||||
player::{Player, PlayerType},
|
||||
server::config::ServerProtectionMode,
|
||||
};
|
||||
|
||||
|
@ -49,6 +49,32 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// gets the packets needed to update a player's inventory
|
||||
pub(crate) fn set_player_inventory(
|
||||
perms: PlayerType,
|
||||
extensions: ExtBitmask,
|
||||
custom_blocks_support_level: u8,
|
||||
packets_queue: &mut Vec<ServerPacket>,
|
||||
) {
|
||||
let custom_blocks =
|
||||
extensions.contains(ExtBitmask::CustomBlocks) && custom_blocks_support_level == 1;
|
||||
assert!(
|
||||
custom_blocks_support_level <= 1,
|
||||
"support not implemented for additional custom block levels"
|
||||
);
|
||||
for (id, info) in &*BLOCK_INFO {
|
||||
if !custom_blocks && *id > 49 {
|
||||
break;
|
||||
}
|
||||
let block = if info.place_permissions <= perms {
|
||||
*id
|
||||
} else {
|
||||
0
|
||||
};
|
||||
packets_queue.push(ServerPacket::SetInventoryOrder { order: *id, block });
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn handle_stream(
|
||||
mut stream: TcpStream,
|
||||
addr: SocketAddr,
|
||||
|
@ -185,13 +211,17 @@ async fn handle_stream_inner(
|
|||
pitch: 0,
|
||||
permissions: player_type,
|
||||
extensions: ExtBitmask::none(),
|
||||
custom_blocks_support_level: 0,
|
||||
packets_to_send: Vec::new(),
|
||||
should_be_kicked: None,
|
||||
};
|
||||
|
||||
if magic_number == EXTENSION_MAGIC_NUMBER {
|
||||
player.extensions = extensions::get_supported_extensions(stream).await?;
|
||||
(player.extensions, player.custom_blocks_support_level) =
|
||||
extensions::get_supported_extensions(stream).await?;
|
||||
}
|
||||
let extensions = player.extensions;
|
||||
let custom_blocks_support_level = player.custom_blocks_support_level;
|
||||
|
||||
reply_queue.push(ServerPacket::ServerIdentification {
|
||||
protocol_version: 0x07,
|
||||
|
@ -201,9 +231,12 @@ async fn handle_stream_inner(
|
|||
});
|
||||
|
||||
println!("generating level packets");
|
||||
reply_queue.extend(build_level_packets(&data.level).into_iter());
|
||||
reply_queue.extend(
|
||||
build_level_packets(&data.level, extensions, custom_blocks_support_level)
|
||||
.into_iter(),
|
||||
);
|
||||
|
||||
if player.extensions.contains(ExtBitmask::EnvWeatherType) {
|
||||
if extensions.contains(ExtBitmask::EnvWeatherType) {
|
||||
reply_queue.push(ServerPacket::EnvWeatherType {
|
||||
weather_type: data.level.weather,
|
||||
});
|
||||
|
@ -263,6 +296,15 @@ async fn handle_stream_inner(
|
|||
reply_queue.push(ServerPacket::UpdateUserType {
|
||||
user_type: player_type,
|
||||
});
|
||||
|
||||
if extensions.contains(ExtBitmask::InventoryOrder) {
|
||||
set_player_inventory(
|
||||
player_type,
|
||||
extensions,
|
||||
custom_blocks_support_level,
|
||||
&mut reply_queue,
|
||||
);
|
||||
}
|
||||
}
|
||||
ClientPacket::SetBlock {
|
||||
x,
|
||||
|
@ -422,14 +464,30 @@ async fn handle_stream_inner(
|
|||
}
|
||||
|
||||
/// 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,
|
||||
extensions: ExtBitmask,
|
||||
custom_blocks_support_level: u8,
|
||||
) -> Vec<ServerPacket> {
|
||||
let mut packets: Vec<ServerPacket> = vec![ServerPacket::LevelInitialize {}];
|
||||
|
||||
// TODO: the type conversions in here may be weird idk
|
||||
let custom_blocks =
|
||||
extensions.contains(ExtBitmask::CustomBlocks) && custom_blocks_support_level >= 1;
|
||||
|
||||
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);
|
||||
data.extend(level.blocks.iter().copied().map(|b| {
|
||||
if custom_blocks || b <= 49 {
|
||||
b
|
||||
} else {
|
||||
BLOCK_INFO
|
||||
.get(&b)
|
||||
.expect("missing block")
|
||||
.fallback
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}));
|
||||
|
||||
let mut e = GzEncoder::new(Vec::new(), Compression::best());
|
||||
e.write_all(&data).expect("failed to gzip level data");
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::packet::{
|
||||
client::ClientPacket, client_extended::ExtendedClientPacket, server::ServerPacket, ExtBitmask,
|
||||
ExtInfo,
|
||||
use crate::{
|
||||
level::block::CUSTOM_BLOCKS_SUPPORT_LEVEL,
|
||||
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> {
|
||||
pub async fn get_supported_extensions(stream: &mut TcpStream) -> std::io::Result<(ExtBitmask, u8)> {
|
||||
let extensions = ExtBitmask::all().all_contained_info();
|
||||
|
||||
write_packets(
|
||||
|
@ -61,5 +64,23 @@ pub async fn get_supported_extensions(stream: &mut TcpStream) -> std::io::Result
|
|||
.into_iter()
|
||||
.fold(ExtBitmask::none(), |acc, ext| acc | ext.bitmask);
|
||||
|
||||
Ok(final_bitmask)
|
||||
let custom_blocks_support_level = if final_bitmask.contains(ExtBitmask::CustomBlocks) {
|
||||
write_packets(
|
||||
stream,
|
||||
Some(ServerPacket::CustomBlockSupportLevel).into_iter(),
|
||||
)
|
||||
.await?;
|
||||
if let Some(ClientPacket::Extended(ExtendedClientPacket::CustomBlockSupportLevel {
|
||||
support_level,
|
||||
})) = next_packet(stream).await?
|
||||
{
|
||||
support_level.min(CUSTOM_BLOCKS_SUPPORT_LEVEL)
|
||||
} else {
|
||||
panic!("expected CustomBlockSupportLevel packet!");
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
Ok((final_bitmask, custom_blocks_support_level))
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue