diff --git a/src/bot.rs b/src/bot.rs index 659a8f3..5fdad7b 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -11,6 +11,9 @@ use strum_macros::EnumIter; pub enum Command { Help, BotStatus, + Whitelist, + WhitelistAdd, + WhitelistRemove, LastLocation, LastOnline, FollowPlayer, @@ -21,6 +24,8 @@ pub enum Command { Slot, UseItem, Look, + Sneak, + Unsneak, ToggleBotStatusMessages, ToggleAlertMessages, Unknown, @@ -44,6 +49,9 @@ pub async fn process_command( match segments[0].to_lowercase().as_str() { "help" => command = Command::Help, "bot_status" => command = Command::BotStatus, + "whitelist" => command = Command::Whitelist, + "whitelist_add" => command = Command::WhitelistAdd, + "whitelist_remove" => command = Command::WhitelistRemove, "last_location" => command = Command::LastLocation, "last_online" => command = Command::LastOnline, "follow_player" => command = Command::FollowPlayer, @@ -54,6 +62,8 @@ pub async fn process_command( "slot" => command = Command::Slot, "use_item" => command = Command::UseItem, "look" => command = Command::Look, + "sneak" => command = Command::Sneak, + "unsneak" => command = Command::Unsneak, "toggle_alert_messages" => command = Command::ToggleAlertMessages, "toggle_bot_status_messages" => command = Command::ToggleBotStatusMessages, _ => (), @@ -70,10 +80,60 @@ pub async fn process_command( return "Commands: ".to_owned() + &commands.join(", "); } Command::BotStatus => { + let bot_status = state.bot_status.lock().unwrap().to_owned(); let metadata = client.metadata(); return format!( - "Health: {}/20, Score: {}, Air Supply: {}", - metadata.health, metadata.score, metadata.air_supply + "Health: {:.1}/20, Food: {}/20, Saturation: {:.1}/20, Score: {}, Air Supply: {}", + bot_status.health, + bot_status.food, + bot_status.saturation, + metadata.score, + metadata.air_supply + ); + } + Command::Whitelist => { + let whitelist = state.whitelist.lock().unwrap().join(", "); + if whitelist.is_empty() { + return "There are no whitelisted players...".to_string(); + } else { + return format!("Whitelisted players: {}", whitelist); + } + } + Command::WhitelistAdd => { + if segments.len() < 1 { + return "Please tell me the name of the player!".to_string(); + } + + let mut whitelist = state.whitelist.lock().unwrap().to_vec(); + if whitelist.contains(&segments[0]) { + return format!("{} is already whitelisted!", segments[0]); + } + whitelist.push(segments[0].to_owned()); + *state.whitelist.lock().unwrap() = whitelist; + return format!( + "{} has been successfully added to the whitelist!", + segments[0] + ); + } + Command::WhitelistRemove => { + if segments.len() < 1 { + return "Please tell me the name of the player!".to_string(); + } + + let mut whitelist = state.whitelist.lock().unwrap().to_vec(); + if !whitelist.contains(&segments[0]) { + return format!("{} is not whitelisted!", segments[0]); + } + whitelist.remove( + whitelist + .iter() + .position(|item| *item == segments[0]) + .unwrap(), + ); + *state.whitelist.lock().unwrap() = whitelist; + return format!( + "{} has been successfully removed from the whitelist!", + segments[0] ); } Command::LastLocation => { @@ -160,7 +220,7 @@ pub async fn process_command( } Command::StopFollowPlayer => { *state.followed_player.lock().unwrap() = None; - let current_position = client.entity().pos().clone(); + let current_position = client.entity().pos().to_owned(); client.goto(BlockPosGoal { pos: BlockPos { x: current_position.x.round() as i32, @@ -203,7 +263,7 @@ pub async fn process_command( ) } Command::StopGoto => { - let current_position = client.entity().pos().clone(); + let current_position = client.entity().pos().to_owned(); client.goto(BlockPosGoal { pos: BlockPos { x: current_position.x.round() as i32, @@ -226,30 +286,32 @@ pub async fn process_command( return "Please give me a slot to set!".to_string(); } - client - .write_packet(ServerboundGamePacket::SetCarriedItem( - game::serverbound_set_carried_item_packet::ServerboundSetCarriedItemPacket { - slot: match segments[0].parse() { - Ok(number) => number, - Err(error) => return format!("Unable to parse slot: {}", error), + log_error( + client + .write_packet(ServerboundGamePacket::SetCarriedItem( + game::serverbound_set_carried_item_packet::ServerboundSetCarriedItemPacket { + slot: match segments[0].parse() { + Ok(number) => number, + Err(error) => return format!("Unable to parse slot: {}", error), + }, }, - }, - )) - .await - .unwrap(); - "Successfully sent a `SetCarriedItem` packet to the server".to_string() + )) + .await + ); + "I have successfully switched slots!".to_string() } Command::UseItem => { - client - .write_packet(ServerboundGamePacket::UseItem( - game::serverbound_use_item_packet::ServerboundUseItemPacket { - hand: InteractionHand::MainHand, - sequence: 0, - }, - )) - .await - .unwrap(); - "Successfully sent a `UseItem` packet to the server".to_string() + log_error( + client + .write_packet(ServerboundGamePacket::UseItem( + game::serverbound_use_item_packet::ServerboundUseItemPacket { + hand: InteractionHand::MainHand, + sequence: 0, + }, + )) + .await, + ); + "I have successfully used the item!".to_string() } Command::Look => { if segments.len() < 2 { @@ -266,6 +328,37 @@ pub async fn process_command( client.set_rotation(rotation[0], rotation[1]); format!("I am now looking at {} {}!", rotation[0], rotation[1]) } + Command::Sneak => { + let entity_id = client.entity_id.read().to_owned(); + log_error( + client + .write_packet(ServerboundGamePacket::PlayerCommand( + game::serverbound_player_command_packet::ServerboundPlayerCommandPacket { + id: entity_id, + action: game::serverbound_player_command_packet::Action::PressShiftKey, + data: 0, + }, + )) + .await, + ); + return "I am now sneaking!".to_string(); + } + Command::Unsneak => { + let entity_id = client.entity_id.read().to_owned(); + log_error( + client + .write_packet(ServerboundGamePacket::PlayerCommand( + game::serverbound_player_command_packet::ServerboundPlayerCommandPacket { + id: entity_id, + action: + game::serverbound_player_command_packet::Action::ReleaseShiftKey, + data: 0, + }, + )) + .await, + ); + return "I am no longer sneaking!".to_string(); + } Command::ToggleAlertMessages => { if state.alert_players.lock().unwrap().contains(executor) { let mut players = state.alert_players.lock().unwrap().to_vec(); diff --git a/src/main.rs b/src/main.rs index f0cb6bf..91744c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,13 +86,13 @@ async fn main() { .collect(); if segments.len() == 1 { ServerAddress { - host: segments[0].clone(), + host: segments[0].to_owned(), port: 25565, } } else if segments.len() == 2 { ServerAddress { - host: segments[0].clone(), - port: segments[1].clone().parse().unwrap_or(25565), + host: segments[0].to_owned(), + port: segments[1].to_owned().parse().unwrap_or(25565), } } else { log_message( @@ -104,13 +104,16 @@ async fn main() { }, state: State { bot_configuration: bot_configuration.clone(), + whitelist: Arc::new(Mutex::new(bot_configuration.clone().whitelist)), logged_in: Arc::new(Mutex::new(false)), + bot_status: Arc::new(Mutex::new(BotStatus::default())), tick_counter: Arc::new(Mutex::new(0)), alert_second_counter: Arc::new(Mutex::new(0)), followed_player: Arc::new(Mutex::new(None)), player_locations: Arc::new(Mutex::new(HashMap::new())), player_timestamps: Arc::new(Mutex::new(HashMap::new())), alert_players: Arc::new(Mutex::new(bot_configuration.clone().alert_players)), + alert_queue: Arc::new(Mutex::new(HashMap::new())), bot_status_players: Arc::new(Mutex::new(Vec::new())), }, plugins: plugins![], @@ -145,16 +148,26 @@ pub struct PlayerTimeData { leave_time: u64, } +#[derive(Default, Debug, Clone)] +pub struct BotStatus { + health: f32, + food: u32, + saturation: f32, +} + #[derive(Default, Debug, Clone)] pub struct State { bot_configuration: BotConfiguration, + whitelist: Arc>>, logged_in: Arc>, + bot_status: Arc>, tick_counter: Arc>, alert_second_counter: Arc>, followed_player: Arc>>, player_locations: Arc>>, player_timestamps: Arc>>, alert_players: Arc>>, + alert_queue: Arc>>>, bot_status_players: Arc>>, } @@ -189,7 +202,7 @@ async fn handle(mut client: Client, event: Event, mut state: State) -> anyhow::R .await? } Event::AddPlayer(player) => { - let mut player_timestamps = state.player_timestamps.lock().unwrap().clone(); + let mut player_timestamps = state.player_timestamps.lock().unwrap().to_owned(); let mut current_player = player_timestamps .get(&player.profile.name) .unwrap_or(&PlayerTimeData { @@ -206,7 +219,7 @@ async fn handle(mut client: Client, event: Event, mut state: State) -> anyhow::R *state.player_timestamps.lock().unwrap() = player_timestamps; } Event::RemovePlayer(player) => { - let mut player_timestamps = state.player_timestamps.lock().unwrap().clone(); + let mut player_timestamps = state.player_timestamps.lock().unwrap().to_owned(); let mut current_player = player_timestamps .get(&player.profile.name) .unwrap_or(&PlayerTimeData { @@ -238,7 +251,7 @@ async fn handle(mut client: Client, event: Event, mut state: State) -> anyhow::R let followed_player = state.followed_player.lock().unwrap().to_owned(); if followed_player.is_some() { - let player_locations = state.player_locations.lock().unwrap().clone(); + let player_locations = state.player_locations.lock().unwrap().to_owned(); match player_locations.get(&followed_player.unwrap()) { Some(position_time_data) => client.goto(BlockPosGoal { pos: BlockPos { @@ -255,82 +268,66 @@ async fn handle(mut client: Client, event: Event, mut state: State) -> anyhow::R if *state.alert_second_counter.lock().unwrap() >= 5 { *state.alert_second_counter.lock().unwrap() = 0; - let player_locations = state.player_locations.lock().unwrap().clone(); - for (player, position_time_data) in player_locations { - if ((state.bot_configuration.alert_location[0] - - state.bot_configuration.alert_radius) - ..(state.bot_configuration.alert_location[0] - + state.bot_configuration.alert_radius)) - .contains(&position_time_data.position[0]) - || ((state.bot_configuration.alert_location[1] - - state.bot_configuration.alert_radius) - ..(state.bot_configuration.alert_location[1] - + state.bot_configuration.alert_radius)) - .contains(&position_time_data.position[2]) - { - if !state.bot_configuration.whitelist.contains(&player.username) { - let alert_players = state.alert_players.lock().unwrap().clone(); - for alert_player in alert_players { - log_error( - client - .send_command_packet(&format!( - "msg {} {}", - alert_player, - format!( - "{} is near our base at {} {} {}!", - player.username, - position_time_data.position[0], - position_time_data.position[1], - position_time_data.position[2], - ) - )) - .await, - ); - } - let mut alert_command = state.bot_configuration.alert_command.to_vec(); - for argument in alert_command.iter_mut() { - *argument = argument.replace("{player_name}", &player.username); - *argument = argument - .replace("{x}", &position_time_data.position[0].to_string()); - *argument = argument - .replace("{y}", &position_time_data.position[1].to_string()); - *argument = argument - .replace("{z}", &position_time_data.position[2].to_string()); - } - if alert_command.len() >= 1 { - log_message(Bot, &"Executing alert shell command...".to_string()); - let command_name = alert_command[0].clone(); - alert_command.remove(0); - log_error( - std::process::Command::new(command_name) - .args(alert_command) - .stdin(std::process::Stdio::null()) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .spawn(), - ); - } - } + let alert_queue = state.alert_queue.lock().unwrap().to_owned(); + for (intruder, position) in alert_queue { + let alert_players = state.alert_players.lock().unwrap().to_vec(); + for alert_player in alert_players { + log_error( + client + .send_command_packet(&format!( + "msg {} {}", + alert_player, + format!( + "{} is near our base at {} {} {}!", + intruder, position[0], position[1], position[2], + ) + )) + .await, + ); + } + let mut alert_command = state.bot_configuration.alert_command.to_vec(); + for argument in alert_command.iter_mut() { + *argument = argument.replace("{player_name}", &intruder); + *argument = argument.replace("{x}", &(position[0]).to_string()); + *argument = argument.replace("{y}", &(position[1]).to_string()); + *argument = argument.replace("{z}", &(position[2]).to_string()); + } + if alert_command.len() >= 1 { + log_message(Bot, &"Executing alert shell command...".to_string()); + let command_name = alert_command[0].clone(); + alert_command.remove(0); + log_error( + std::process::Command::new(command_name) + .args(alert_command) + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn(), + ); } } + *state.alert_queue.lock().unwrap() = HashMap::new(); } } Event::Packet(packet) => match packet.as_ref() { ClientboundGamePacket::MoveEntityPos(packet) => { - let world = client.world.read(); - let entity = match world.entity(packet.entity_id) { - Some(entity) => entity, - None => return Ok(()), - }; - for (uuid, player) in client.players.read().iter() { - if uuid.as_u128() == entity.uuid.as_u128() { - let position = entity.pos(); - let mut player_locations = state.player_locations.lock().unwrap().clone(); + let entity: (u128, u32, azalea::Vec3) = + match (client.world.read()).entity(packet.entity_id) { + Some(entity) => (entity.uuid.as_u128(), entity.id, entity.pos().to_owned()), + None => return Ok(()), + }; + let players = client.players.read().to_owned(); + for (uuid, player) in players.iter().map(|item| item.to_owned()) { + if uuid.as_u128() == entity.0 { + let position = entity.2; + + let mut player_locations = + state.player_locations.lock().unwrap().to_owned(); player_locations.insert( Player { uuid: uuid.as_u128(), - entity_id: entity.id, - username: player.profile.name.clone(), + entity_id: entity.1, + username: player.profile.name.to_owned(), }, PositionTimeData { position: vec![ @@ -345,10 +342,41 @@ async fn handle(mut client: Client, event: Event, mut state: State) -> anyhow::R }, ); *state.player_locations.lock().unwrap() = player_locations; + + if ((state.bot_configuration.alert_location[0] + - state.bot_configuration.alert_radius) + ..(state.bot_configuration.alert_location[0] + + state.bot_configuration.alert_radius)) + .contains(&(position.x as i32)) + && ((state.bot_configuration.alert_location[1] + - state.bot_configuration.alert_radius) + ..(state.bot_configuration.alert_location[1] + + state.bot_configuration.alert_radius)) + .contains(&(position.z as i32)) + { + if !state + .whitelist + .lock() + .unwrap() + .contains(&player.profile.name) + { + let mut alert_queue = state.alert_queue.lock().unwrap().to_owned(); + alert_queue.insert( + player.profile.name.to_owned(), + vec![position.x as i32, position.y as i32, position.z as i32], + ); + *state.alert_queue.lock().unwrap() = alert_queue; + } + } } } } ClientboundGamePacket::SetHealth(packet) => { + *state.bot_status.lock().unwrap() = BotStatus { + health: packet.health, + food: packet.food, + saturation: packet.saturation, + }; let bot_status_players: Vec = state .bot_status_players .lock() @@ -404,7 +432,7 @@ async fn handle(mut client: Client, event: Event, mut state: State) -> anyhow::R return Ok(()); } - for bot_owner in state.bot_configuration.bot_owners.clone() { + for bot_owner in state.bot_configuration.bot_owners.to_owned() { if message .message() .to_string() @@ -435,7 +463,7 @@ async fn handle(mut client: Client, event: Event, mut state: State) -> anyhow::R ); } - let mut player_timestamps = state.player_timestamps.lock().unwrap().clone(); + let mut player_timestamps = state.player_timestamps.lock().unwrap().to_owned(); let mut current_player = player_timestamps .get(&message.username().unwrap_or("Someone".to_string())) .unwrap_or(&PlayerTimeData {