implement commands system

This commit is contained in:
Zoey 2024-04-22 22:02:06 -07:00
parent 37c82d8755
commit 19116ee308
No known key found for this signature in database
GPG key ID: 8611B896D1AAFAF2
5 changed files with 248 additions and 57 deletions

77
src/command.rs Normal file
View file

@ -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 `*<username>`
///
/// i.e. `/me says hello` becomes `*<username> 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<Command, String> {
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)
}
}

View file

@ -7,6 +7,7 @@ use server::{
Server, Server,
}; };
mod command;
mod level; mod level;
mod packet; mod packet;
mod player; mod player;

View file

@ -127,7 +127,7 @@ impl ServerPacket {
.write_u8(*protocol_version) .write_u8(*protocol_version)
.write_string(server_name) .write_string(server_name)
.write_string(server_motd) .write_string(server_motd)
.write_u8(*user_type as u8), .write_u8(user_type.into()),
Self::Ping {} => writer, Self::Ping {} => writer,
Self::LevelInitialize {} => writer, Self::LevelInitialize {} => writer,
Self::LevelDataChunk { Self::LevelDataChunk {
@ -220,7 +220,7 @@ impl ServerPacket {
writer.write_i8(*player_id).write_string(message) writer.write_i8(*player_id).write_string(message)
} }
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 as u8), Self::UpdateUserType { user_type } => writer.write_u8(user_type.into()),
} }
} }

View file

@ -33,12 +33,13 @@ pub struct Player {
/// enum describing types of players /// 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)]
#[repr(u8)]
pub enum PlayerType { pub enum PlayerType {
/// a normal player /// a normal player
Normal = 0x00, Normal,
/// moderator of the server
Moderator,
/// a player who's an operator /// a player who's an operator
Operator = 0x64, Operator,
} }
impl Default for PlayerType { impl Default for PlayerType {
@ -47,16 +48,25 @@ impl Default for PlayerType {
} }
} }
impl TryFrom<u8> for PlayerType { impl From<&PlayerType> for u8 {
type Error = (); fn from(val: &PlayerType) -> Self {
match val {
fn try_from(value: u8) -> Result<Self, Self::Error> { PlayerType::Normal => 0,
if value == Self::Normal as u8 { PlayerType::Moderator => 0x64,
Ok(Self::Normal) PlayerType::Operator => 0x64,
} else if value == Self::Operator as u8 {
Ok(Self::Operator)
} else {
Err(())
} }
} }
} }
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}")),
})
}
}

View file

@ -10,6 +10,7 @@ use tokio::{
}; };
use crate::{ use crate::{
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, PacketWriter, ARRAY_LENGTH},
player::{Player, PlayerType}, player::{Player, PlayerType},
@ -72,6 +73,24 @@ async fn handle_stream_inner(
let mut read_buf; let mut read_buf;
let mut id_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 { loop {
let ready = stream let ready = stream
.ready(Interest::READABLE | Interest::WRITABLE) .ready(Interest::READABLE | Interest::WRITABLE)
@ -211,10 +230,7 @@ async fn handle_stream_inner(
player.packets_to_send.push(message_packet.clone()); player.packets_to_send.push(message_packet.clone());
} }
} }
reply_queue.push_back(ServerPacket::Message { msg!("&dWelcome to the server! Enjoyyyyyy".to_string());
player_id: *own_id,
message: "Welcome to the server! Enjoyyyyyy".to_string(),
});
reply_queue.push_back(ServerPacket::UpdateUserType { reply_queue.push_back(ServerPacket::UpdateUserType {
user_type: PlayerType::Operator, user_type: PlayerType::Operator,
}); });
@ -241,13 +257,7 @@ async fn handle_stream_inner(
let new_block_info = BLOCK_INFO.get(&block_type); let new_block_info = BLOCK_INFO.get(&block_type);
if new_block_info.is_none() { if new_block_info.is_none() {
reply_queue.push_back(ServerPacket::Message { msg!(format!("&cUnknown block ID: 0x{:0x}", block_type));
player_id: -1,
message: format!(
"Unknown block ID: 0x{:0x}",
block_type
),
});
continue; continue;
} }
let new_block_info = new_block_info.expect("will never fail"); let new_block_info = new_block_info.expect("will never fail");
@ -266,16 +276,10 @@ async fn handle_stream_inner(
.unwrap_or_default(); .unwrap_or_default();
if player_type < new_block_info.place_permissions { if player_type < new_block_info.place_permissions {
cancel = true; cancel = true;
reply_queue.push_back(ServerPacket::Message { msg!("&cNot allow to place this block.".to_string());
player_id: -1,
message: "Not allowed to place this block.".to_string(),
});
} else if player_type < block_info.break_permissions { } else if player_type < block_info.break_permissions {
cancel = true; cancel = true;
reply_queue.push_back(ServerPacket::Message { msg!("&cNot allowed to break this block.".to_string());
player_id: -1,
message: "Not allowed to break this block.".to_string(),
});
} }
if cancel { if cancel {
@ -305,21 +309,119 @@ async fn handle_stream_inner(
yaw, yaw,
pitch, pitch,
} => { } => {
let packet = ServerPacket::SetPositionOrientation { let mut data = data.write().await;
spread_packet!(
data,
ServerPacket::SetPositionOrientation {
player_id: *own_id, player_id: *own_id,
x, x,
y, y,
z, z,
yaw, yaw,
pitch, 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 } => { ClientPacket::Message { player_id, message } => {
let mut data = data.write().await; let mut data = data.write().await;
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}"); println!("{message}");
let message = format!( let message = format!(
"&f<{}> {message}", "&f<{}> {message}",
@ -329,9 +431,10 @@ async fn handle_stream_inner(
.expect("should never fail") .expect("should never fail")
.username .username
); );
let packet = ServerPacket::Message { player_id, message }; spread_packet!(
for player in &mut data.players { data,
player.packets_to_send.push(packet.clone()); ServerPacket::Message { player_id, message }
);
} }
} }
} }