diff --git a/src/bot.rs b/src/bot.rs index d1835bd..873a2d1 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -8,6 +8,7 @@ use std::{ }; use hashbrown::HashMap; +use log::{debug, info}; use tokio::runtime::Runtime; use vek::{Quaternion, Vec3}; use veloren_client::{addr::ConnectionArgs, Client, Event as VelorenEvent, SiteInfoRich, WorldExt}; @@ -83,7 +84,7 @@ impl Bot { orientation: Option, announcement: Option, ) -> Result { - log::info!("Connecting to veloren"); + info!("Connecting to veloren"); let mut client = connect_to_veloren(game_server, auth_server, &username, password)?; let mut clock = Clock::new(CLIENT_TPS); @@ -107,7 +108,7 @@ impl Bot { .id .ok_or("Failed to get character ID")?; - log::info!("Selecting a character"); + info!("Selecting a character"); // This loop waits and retries requesting the character in the case that the character has // logged out too recently. @@ -162,7 +163,8 @@ impl Bot { }) } - /// Run the bot for a single tick. This should be called in a loop. + /// Run the bot for a single tick. This should be called in a loop. Returns `true` if the loop + /// should continue running. /// /// There are three timers in this function: /// - The [Clock] runs the Veloren client. At **30 ticks per second** this timer is faster than @@ -174,14 +176,18 @@ impl Bot { /// This function should be modified with care. In addition to being the bot's main loop, it /// also accepts incoming trade invites, which has a potential for error if the bot accepts an /// invite while in the wrong trade mode. - pub fn tick(&mut self) -> Result<(), String> { + pub fn tick(&mut self) -> Result { let veloren_events = self .client .tick(ControllerInputs::default(), self.clock.dt()) .map_err(|error| format!("{error:?}"))?; for event in veloren_events { - self.handle_veloren_event(event)?; + let should_continue = self.handle_veloren_event(event)?; + + if !should_continue { + return Ok(false); + } } if self.last_trade_action.elapsed() > TRADE_ACTION_DELAY { @@ -218,9 +224,9 @@ impl Bot { self.sort_count -= 1; if self.sort_count == 0 { - log::info!("Sorted inventory, finished") + debug!("Sorted inventory, finished") } else { - log::info!("Sorted inventory, {} more times to go", self.sort_count); + debug!("Sorted inventory, {} more times to go", self.sort_count); } } @@ -235,17 +241,18 @@ impl Bot { self.clock.tick(); - Ok(()) + Ok(true) } - /// Consume and manage a client-side Veloren event. - fn handle_veloren_event(&mut self, event: VelorenEvent) -> Result<(), String> { + /// Consume and manage a client-side Veloren event. Returns a boolean indicating whether the + /// bot should continue processing events. + fn handle_veloren_event(&mut self, event: VelorenEvent) -> Result { match event { VelorenEvent::Chat(message) => { let sender = if let ChatType::Tell(uid, _) = message.chat_type { uid } else { - return Ok(()); + return Ok(true); }; let content = message.content().as_plain().unwrap_or_default(); let mut split_content = content.split(' '); @@ -254,7 +261,7 @@ impl Bot { let correction_message = match command { "admin_access" => { if self.is_user_admin(&sender)? && !self.client.is_trading() { - log::info!("Providing admin access"); + info!("Providing admin access"); self.previous_trade = None; self.trade_mode = TradeMode::AdminAccess; @@ -330,13 +337,13 @@ impl Bot { .parse::() .map_err(|error| error.to_string())?; - log::info!("Sorting inventory {sort_count} times"); + debug!("Sorting inventory {sort_count} times"); self.sort_count = sort_count; } else { self.client.sort_inventory(); - log::info!("Sorting inventory once"); + debug!("Sorting inventory once"); } None @@ -374,7 +381,7 @@ impl Bot { } VelorenEvent::Outcome(Outcome::HealthChange { info, .. }) => { if let Some(DamageSource::Buff(_)) = info.cause { - return Ok(()); + return Ok(true); } if let Some(uid) = self.client.uid() { @@ -403,7 +410,7 @@ impl Bot { match result { TradeResult::Completed => { if let Some(reciept) = &self.previous_trade_receipt { - log::info!("Trade with {their_name}: {:?}", reciept); + info!("Trade with {their_name}: {:?}", reciept); } self.client.send_command( @@ -411,22 +418,25 @@ impl Bot { vec!["Thank you for trading with me!".to_string()], ); } - TradeResult::Declined => log::info!("Trade with {their_name} declined"), + TradeResult::Declined => info!("Trade with {their_name} declined"), TradeResult::NotEnoughSpace => { - log::info!("Trade with {their_name} failed: not enough space") + info!("Trade with {their_name} failed: not enough space") } } if let TradeMode::AdminAccess = self.trade_mode { - log::info!("End of admin access for {their_name}"); + info!("End of admin access for {their_name}"); self.trade_mode = TradeMode::Trade; } } + VelorenEvent::Disconnect => { + return Ok(false); + } _ => (), } - Ok(()) + Ok(true) } /// Make the bot's trading and help accouncements @@ -435,7 +445,7 @@ impl Bot { /// is always made. If an announcement was provided when the bot was created, it will make it /// in /world. fn handle_announcement(&mut self) -> Result<(), String> { - log::info!("Making an announcement"); + debug!("Making an announcement"); self.client.send_command( "region".to_string(), @@ -507,10 +517,10 @@ impl Bot { /// 4. If the trade is balanced, accept it. /// 5. If the total value of their offer is greater than the total value of my offer: /// 1. If they are offering coins, remove them to balance. - /// 2. If they are not offering coins, add mine to balance. + /// 2. If they are not offering coins, add mine to balance if I have enough. /// 6. If the total value of my offer is greater than the total value of their offer: /// 1. If I am offering coins, remove them to balance. - /// 2. If I am not offering coins, add theirs to balance. + /// 2. If I am not offering coins, add theirs to balance if they have enough. /// 7. If the trade is still unbalanced, tell them the value of the greater offer and the /// other party's total coin amount. /// @@ -680,7 +690,7 @@ impl Bot { // offer now. The trade action is infallible from here. self.previous_trade = Some(trade); - log::info!("Performing trade action with {their_name}"); + debug!("Performing trade action with {their_name}"); // Before running any actual trade logic, remove items that are not for sale or not being // purchased. End this trade action if an item was removed. @@ -709,7 +719,8 @@ impl Bot { // The if/else statements below implement the bot's main feature: buying, selling and // trading items according to the values set in the configuration file. Coins are used to - // balance the value of the trade. + // balance the value of the trade. If there are not enough or no coins to balance the + // trade, a message is sent to the user. // If the trade is balanced if difference == 0 { @@ -726,19 +737,18 @@ impl Bot { // If they are offering coins if their_offered_coin_amount > 0 { if let Some(their_coins) = get_their_coins { - if their_coin_amount >= difference as u32 { - // Remove their coins to balance - self.client.perform_trade_action(TradeAction::RemoveItem { - item: their_coins, - quantity: difference as u32, - ours: false, - }); + // Remove their coins to balance + self.client.perform_trade_action(TradeAction::RemoveItem { + item: their_coins, + quantity: difference as u32, + ours: false, + }); - return Ok(()); - } + return Ok(()); } // If they are not offering coins } else if let Some(my_coins) = get_my_coins { + // If I have enough coins if my_coin_amount >= difference as u32 { // Add my coins to balanace self.client.perform_trade_action(TradeAction::AddItem { @@ -760,6 +770,8 @@ impl Bot { ), ], ); + + return Ok(()); } // If I am offering more @@ -767,19 +779,18 @@ impl Bot { // If I am offering coins if my_offered_coin_amount > 0 { if let Some(my_coins) = get_my_coins { - if my_coin_amount >= difference.unsigned_abs() { - // Remove my coins to balance - self.client.perform_trade_action(TradeAction::RemoveItem { - item: my_coins, - quantity: difference.unsigned_abs(), - ours: true, - }); + // Remove my coins to balance + self.client.perform_trade_action(TradeAction::RemoveItem { + item: my_coins, + quantity: difference.unsigned_abs(), + ours: true, + }); - return Ok(()); - } + return Ok(()); } // If I am not offering coins } else if let Some(their_coins) = get_their_coins { + // If they have enough coins if their_coin_amount >= difference.unsigned_abs() { // Add their coins to balance self.client.perform_trade_action(TradeAction::AddItem { @@ -792,8 +803,6 @@ impl Bot { } } - let their_name = self.find_player_alias(&their_uid).unwrap().clone(); - self.client.send_command( "tell".to_string(), vec![ @@ -858,7 +867,7 @@ impl Bot { let total_found = buying.len() + selling.len(); if total_found == 0 { - log::info!("Found no price for \"{original_search_term}\" for {player_name}"); + debug!("Found no price for \"{original_search_term}\" for {player_name}"); self.client.send_command( "tell".to_string(), @@ -872,7 +881,7 @@ impl Bot { } if total_found > 10 { - log::info!( + debug!( "Found {total_found} prices for \"{original_search_term}\" for {player_name}, not sending." ); @@ -889,7 +898,7 @@ impl Bot { return Ok(()); } - log::info!("Found {total_found} prices for \"{original_search_term}\" for {player_name}, sending prices."); + debug!("Found {total_found} prices for \"{original_search_term}\" for {player_name}, sending prices."); for (item_name, price) in buying { self.client.send_command( @@ -934,6 +943,8 @@ impl Bot { fn handle_position_and_orientation(&mut self) -> Result<(), String> { if let Some(current_position) = self.client.current::() { if current_position != self.position { + debug!("Updating position to {}", self.position.0); + let entity = self.client.entity(); let ecs = self.client.state_mut().ecs(); let mut position_state = ecs.write_storage::(); @@ -946,6 +957,8 @@ impl Bot { if let Some(current_orientation) = self.client.current::() { if current_orientation != self.orientation { + debug!("Updating orientation to {:?}", self.orientation); + let entity = self.client.entity(); let ecs = self.client.state_mut().ecs(); let mut orientation_state = ecs.write_storage::(); diff --git a/src/main.rs b/src/main.rs index ef82600..3e85be8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,11 @@ use std::{env::var, fs::read_to_string}; use bot::Bot; use config::{Config, Secrets}; +use env_logger::Env; +use log::error; fn main() { - env_logger::init(); + env_logger::Builder::from_env(Env::default().default_filter_or("debug")).init(); let secrets = { let secrets_path = @@ -47,6 +49,12 @@ fn main() { .expect("Failed to create bot"); loop { - let _ = bot.tick().inspect_err(|error| log::error!("{error}")); + match bot.tick() { + Ok(true) => return, + Ok(false) => {} + Err(error) => { + error!("{error}"); + } + } } }