diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..dcd447b --- /dev/null +++ b/src/command.rs @@ -0,0 +1,77 @@ +use crate::player::PlayerType; + +const CMD_ME: &str = "me"; +const CMD_SAY: &str = "say"; +const CMD_SET_PERM: &str = "set-perm"; + +/// enum for possible commands +#[derive(Debug, Clone)] +pub enum Command<'m> { + /// for rp, prefixes `action` with `*` + /// + /// i.e. `/me says hello` becomes `* says hello` + Me { action: &'m str }, + /// sends a message prefixed with `[SERVER]` instead of the player's username + Say { message: &'m str }, + /// sets permissions for a player + SetPermissions { + player_username: &'m str, + permissions: PlayerType, + }, +} + +impl<'m> Command<'m> { + pub const PREFIX: char = '/'; + + pub fn parse(input: &'m str) -> Result { + let (command_name, mut arguments) = input.split_once(' ').unwrap_or((input, "")); + Ok(match command_name { + CMD_ME => Self::Me { action: arguments }, + CMD_SAY => Self::Say { message: arguments }, + CMD_SET_PERM => Self::SetPermissions { + player_username: Self::next_string(&mut arguments)?, + permissions: arguments.trim().try_into()?, + }, + _ => return Err(format!("Unknown command: {command_name}")), + }) + } + + pub fn perms_required(&self) -> PlayerType { + match self { + Self::Me { .. } => PlayerType::Normal, + _ => PlayerType::Moderator, + } + } + + fn next_string(args: &mut &'m str) -> Result<&'m str, String> { + if args.is_empty() { + return Err("Missing argument".to_string()); + } + + let (start_index, end_index, extra) = if args.starts_with('"') { + let mut end_index = 1; + let mut extra = 1; + while end_index < args.len() { + if let Some(index) = args[end_index..].find('"') { + end_index += index; + if &args[end_index - 1..=end_index - 1] == "\\" { + } else { + break; + } + } else { + end_index = args.len(); + extra = 0; + break; + } + } + (1, end_index, extra) + } else { + (0, args.find(' ').unwrap_or(args.len()), 0) + }; + + let result = &args[start_index..end_index]; + *args = &args[end_index + extra..]; + + Ok(result) + } +} diff --git a/src/main.rs b/src/main.rs index 765b16b..c7adf6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use server::{ Server, }; +mod command; mod level; mod packet; mod player; diff --git a/src/packet/server.rs b/src/packet/server.rs index 9991c68..578bd48 100644 --- a/src/packet/server.rs +++ b/src/packet/server.rs @@ -127,7 +127,7 @@ impl ServerPacket { .write_u8(*protocol_version) .write_string(server_name) .write_string(server_motd) - .write_u8(*user_type as u8), + .write_u8(user_type.into()), Self::Ping {} => writer, Self::LevelInitialize {} => writer, Self::LevelDataChunk { @@ -220,7 +220,7 @@ impl ServerPacket { 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), + Self::UpdateUserType { user_type } => writer.write_u8(user_type.into()), } } diff --git a/src/player.rs b/src/player.rs index 5f24fce..d056884 100644 --- a/src/player.rs +++ b/src/player.rs @@ -33,12 +33,13 @@ pub struct Player { /// enum describing types of players #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[repr(u8)] pub enum PlayerType { /// a normal player - Normal = 0x00, + Normal, + /// moderator of the server + Moderator, /// a player who's an operator - Operator = 0x64, + Operator, } impl Default for PlayerType { @@ -47,16 +48,25 @@ impl Default for PlayerType { } } -impl TryFrom for PlayerType { - type Error = (); - - fn try_from(value: u8) -> Result { - if value == Self::Normal as u8 { - Ok(Self::Normal) - } else if value == Self::Operator as u8 { - Ok(Self::Operator) - } else { - Err(()) +impl From<&PlayerType> for u8 { + fn from(val: &PlayerType) -> Self { + match val { + PlayerType::Normal => 0, + PlayerType::Moderator => 0x64, + PlayerType::Operator => 0x64, } } } + +impl TryFrom<&str> for PlayerType { + type Error = String; + + fn try_from(value: &str) -> Result { + Ok(match value.to_lowercase().as_str() { + "normal" => Self::Normal, + "moderator" => Self::Moderator, + "operator" => Self::Operator, + value => return Err(format!("Unknown permissions type: {value}")), + }) + } +} diff --git a/src/server/network.rs b/src/server/network.rs index 5e5df30..ecf6820 100644 --- a/src/server/network.rs +++ b/src/server/network.rs @@ -10,6 +10,7 @@ use tokio::{ }; use crate::{ + command::Command, level::{block::BLOCK_INFO, BlockUpdate, Level}, packet::{client::ClientPacket, server::ServerPacket, PacketWriter, ARRAY_LENGTH}, player::{Player, PlayerType}, @@ -72,6 +73,24 @@ async fn handle_stream_inner( let mut read_buf; let mut id_buf; + macro_rules! msg { + ($message:expr) => { + reply_queue.push_back(ServerPacket::Message { + player_id: -1, + message: $message, + }); + }; + } + + macro_rules! spread_packet { + ($data:expr, $packet:expr) => { + let packet = $packet; + for player in &mut $data.players { + player.packets_to_send.push(packet.clone()); + } + }; + } + loop { let ready = stream .ready(Interest::READABLE | Interest::WRITABLE) @@ -211,10 +230,7 @@ async fn handle_stream_inner( 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(), - }); + msg!("&dWelcome to the server! Enjoyyyyyy".to_string()); reply_queue.push_back(ServerPacket::UpdateUserType { user_type: PlayerType::Operator, }); @@ -241,13 +257,7 @@ async fn handle_stream_inner( let new_block_info = BLOCK_INFO.get(&block_type); if new_block_info.is_none() { - reply_queue.push_back(ServerPacket::Message { - player_id: -1, - message: format!( - "Unknown block ID: 0x{:0x}", - block_type - ), - }); + msg!(format!("&cUnknown block ID: 0x{:0x}", block_type)); continue; } let new_block_info = new_block_info.expect("will never fail"); @@ -266,16 +276,10 @@ async fn handle_stream_inner( .unwrap_or_default(); if player_type < new_block_info.place_permissions { cancel = true; - reply_queue.push_back(ServerPacket::Message { - player_id: -1, - message: "Not allowed to place this block.".to_string(), - }); + msg!("&cNot allow to place this block.".to_string()); } else if player_type < block_info.break_permissions { cancel = true; - reply_queue.push_back(ServerPacket::Message { - player_id: -1, - message: "Not allowed to break this block.".to_string(), - }); + msg!("&cNot allowed to break this block.".to_string()); } if cancel { @@ -305,33 +309,132 @@ async fn handle_stream_inner( 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()); - } + spread_packet!( + data, + ServerPacket::SetPositionOrientation { + player_id: *own_id, + x, + y, + z, + yaw, + pitch, + } + ); } 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()); + + if let Some(message) = message.strip_prefix(Command::PREFIX) { + match Command::parse(message) { + Ok(cmd) => { + let player = data + .players + .iter() + .find(|p| p.id == *own_id) + .expect("missing player"); + + if cmd.perms_required() > player.player_type { + msg!("Permissions do not allow you to use this command".to_string()); + continue; + } + + match cmd { + Command::Me { action } => { + let message = format!( + "&f*{} {action}", + data.players + .iter() + .find(|p| p.id == *own_id) + .expect("missing player") + .username + ); + spread_packet!( + data, + ServerPacket::Message { + player_id, + message, + } + ); + } + Command::Say { message } => { + let message = + format!("&d[SERVER] &f{message}"); + spread_packet!( + data, + ServerPacket::Message { + player_id, + message, + } + ); + } + Command::SetPermissions { + player_username, + permissions, + } => { + let player_perms = player.player_type; + if player_username == player.username { + msg!("Cannot change your own permissions".to_string()); + continue; + } else if permissions >= player_perms { + msg!("Cannot set permissions higher or equal to your own".to_string()); + continue; + } + + let perm_string = + serde_json::to_string(&permissions) + .expect("should never fail"); + + let current = data + .config + .player_perms + .entry(player_username.to_string()) + .or_default(); + if *current >= player_perms { + msg!("This player outranks you" + .to_string()); + continue; + } + + *current = permissions; + if let Some(p) = data + .players + .iter_mut() + .find(|p| p.username == player_username) + { + p.player_type = permissions; + p.packets_to_send.push( + ServerPacket::UpdateUserType { + user_type: p.player_type, + }, + ); + p.packets_to_send.push(ServerPacket::Message { + player_id: p.id, + message: format!("Your permissions have been set to {perm_string}") + }); + } + msg!(format!("Set permissions for {player_username} to {perm_string}")); + } + } + } + Err(msg) => { + msg!(format!("&c{msg}")); + } + } + } else { + println!("{message}"); + let message = format!( + "&f<{}> {message}", + data.players + .iter() + .find(|p| p.id == *own_id) + .expect("should never fail") + .username + ); + spread_packet!( + data, + ServerPacket::Message { player_id, message } + ); } } }