diff --git a/README.md b/README.md index 4cc4019..6e0b16b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Create a "secrets.toml" file: # secrets.toml username = "my_username" password = "my_password" +character = "my_character" ``` Then create a secret to pass the file securely to the container. @@ -29,7 +30,6 @@ You will also need a "config.toml": ```toml # config.toml -character = "my_character" position = [0.0, 0.0, 0.0] # Change these to the desired X, Y, Z coordinates orientation = "West" diff --git a/config/config.toml b/config/config.toml index aefc997..4b36c3d 100644 --- a/config/config.toml +++ b/config/config.toml @@ -1,4 +1,3 @@ -character = "crabobot_test" position = [17726.0, 14957.0, 237.0] orientation = "West" @@ -6,6 +5,8 @@ orientation = "West" "common.items.food.cheese" = 50 [sell_prices] + +# Boreal Armor "common.items.armor.boreal.back" = 250_000 "common.items.armor.boreal.belt" = 250_000 "common.items.armor.boreal.chest" = 250_000 @@ -13,31 +14,44 @@ orientation = "West" "common.items.armor.boreal.hand" = 250_000 "common.items.armor.boreal.pants" = 250_000 "common.items.armor.boreal.shoulder" = 250_000 - "common.items.armor.misc.head.boreal_warhelm" = 450_000 -"common.items.armor.misc.head.cat_capuche" = 600_000 + +# Hats +"common.items.armor.misc.head.cat_capuche" = 700_000 "common.items.armor.misc.head.hare_hat" = 100_000 "common.items.armor.misc.head.winged_coronet" = 40_000 +"common.items.calendar.christmas.armor.misc.head.woolly_wintercap" = 800_000 +# Crafting "common.items.crafting_ing.brinestone" = 2000 "common.items.crafting_ing.coral_brach" = 1000 "common.items.crafting_ing.dwarven_battery" = 40000 - -"common.items.consumable.potion_minor" = 150 - -"common.items.glider.skullgrin" = 20_000 - "common.items.log.eldwood" = 3000 - "common.items.mineral.ingot.orichalcum" = 8000 +# Potions +"common.items.consumable.potion_minor" = 150 + +# Gliders +"common.items.glider.skullgrin" = 20_000 + +# Recipes +"common.items.recipes.equipment.advanced" = 8000 +"common.items.recipes.unique.mindflayer_spellbag" = 10000 +"common.items.recipes.unique.abyssal_gorget" = 6000 + +# Instruments "common.items.tool.instruments.icy_talharpa" = 500_000 "common.items.tool.instruments.steeltonguedrum" = 300_000 +# Legendary Weapons "common.items.weapons.axe.parashu" = 100_000 -"common.items.weapons.sword.caladbolg" = 100_000 +"common.items.weapons.sword.caladbolg" = 150_000 "common.items.weapons.staff.laevateinn" = 60_000 -"common.items.weapons.hammer.mjolnir" = 100_000 +"common.items.weapons.hammer.mjolnir" = 150_000 "common.items.weapons.sceptre.caduceus" = 150_000 -"common.items.lantern.geode_purple" = 20_000 +# Lanterns +"common.items.boss_drops.lantern" = 40_000 # Magic Lantern +"common.items.lantern.blue_0" = 20_000 +"common.items.lantern.geode_purp" = 20_000 diff --git a/src/bot.rs b/src/bot.rs index de3f9a2..97752cb 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -33,10 +33,10 @@ pub struct Bot { sell_prices: HashMap, trade_mode: TradeMode, - is_player_notified: bool, last_action: Instant, last_announcement: Instant, last_ouch: Instant, + last_cost_difference: i32, } impl Bot { @@ -95,10 +95,10 @@ impl Bot { buy_prices, sell_prices, trade_mode: TradeMode::Trade, - is_player_notified: false, last_action: now, last_announcement: now, last_ouch: now, + last_cost_difference: 0, }) } @@ -135,7 +135,7 @@ impl Bot { if self.last_announcement.elapsed() > Duration::from_secs(1200) { self.client.send_command( "region".to_string(), - vec!["I'm a bot. You can trade with me or use /say or /tell to check prices: 'price [item_name]' or 'prices'.".to_string()], + vec!["I'm a bot. You can trade with me or use /say or /tell to check prices: 'price [item_name]'".to_string()], ); self.last_announcement = Instant::now(); @@ -158,58 +158,12 @@ impl Bot { }; let content = message.content().as_plain().unwrap_or_default(); let (command, item_name) = content.split_once(' ').unwrap_or((content, "")); + let item_name = item_name.to_lowercase(); match command { "price" => { - let player_name = self - .find_name(&sender) - .ok_or("Failed to find player name")? - .to_string(); - let mut found = false; - - for (item_id, price) in &self.buy_prices { - if item_id.contains(item_name) { - log::debug!("Sending price info on {item_id} to {player_name}"); - - self.client.send_command( - "tell".to_string(), - vec![ - player_name.clone(), - format!("I buy {item_id} for {price} coins."), - ], - ); - - found = true; - } - } - - for (item_id, price) in &self.sell_prices { - if item_id.contains(item_name) { - log::debug!("Sending price info on {item_id} to {player_name}"); - - self.client.send_command( - "tell".to_string(), - vec![ - player_name.clone(), - format!("I sell {item_id} for {price} coins."), - ], - ); - - found = true; - } - } - - if !found { - self.client.send_command( - "tell".to_string(), - vec![ - player_name.clone(), - format!("I don't have a price for that item."), - ], - ); - } + self.send_price_info(&sender, &item_name)?; } - "prices" => self.send_all_price_info(&sender)?, "take" => { if !self.client.is_trading() { self.trade_mode = TradeMode::Take; @@ -249,7 +203,6 @@ impl Bot { log::info!("Completed trade: {result:?}"); if let TradeMode::Take = self.trade_mode { - self.is_player_notified = false; self.trade_mode = TradeMode::Trade; } @@ -289,12 +242,6 @@ impl Bot { .ok_or("Failed to get offer index")?; let their_offer_index = if my_offer_index == 0 { 1 } else { 0 }; - if !self.is_player_notified { - self.send_all_price_info(&trade.parties[their_offer_index])?; - - self.is_player_notified = true; - } - if trade.is_empty_trade() { return Ok(()); } @@ -311,16 +258,14 @@ impl Bot { ) }; let inventories = self.client.inventories(); - let my_inventory = inventories.get(self.client.entity()).unwrap(); - let my_coins = my_inventory - .get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string())) - .ok_or("Failed to find coins".to_string())?; - let their_inventory = inventories - .get(them) - .ok_or("Failed to find inventory".to_string())?; - let their_coins = their_inventory - .get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string())) - .ok_or("Failed to find coins")?; + let my_inventory = inventories + .get(self.client.entity()) + .ok_or("Failed to find inventory")?; + let get_my_coins = my_inventory + .get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string())); + let their_inventory = inventories.get(them).ok_or("Failed to find inventory")?; + let get_their_coins = their_inventory + .get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string())); let (mut their_offered_coin_amount, mut my_offered_coin_amount) = (0, 0); let their_offered_items_value = their_offer @@ -439,6 +384,13 @@ impl Bot { return Ok(()); } + let (my_coins, their_coins) = + if let (Some(mine), Some(theirs)) = (get_my_coins, get_their_coins) { + (mine, theirs) + } else { + return Ok(()); + }; + let difference = their_offered_items_value - my_offered_items_value; // If the trade is balanced @@ -447,7 +399,6 @@ impl Bot { self.client .perform_trade_action(TradeAction::Accept(trade.phase)); - return Ok(()); // If they are offering more } else if difference.is_positive() { // If they are offering coins @@ -488,6 +439,22 @@ impl Bot { } } + if difference != self.last_cost_difference { + let their_name = self + .find_name(&trade.parties[their_offer_index]) + .ok_or("Failed to find player name")? + .to_string(); + + log::debug!("Sending cost to {their_name}"); + + self.client.send_command( + "tell".to_string(), + vec![their_name, format!("My offer: {my_offered_items_value}. Your offer: {their_offered_items_value}.")], + ); + } + + self.last_cost_difference = difference; + Ok(()) } @@ -511,23 +478,60 @@ impl Bot { Ok(()) } - fn send_all_price_info(&mut self, target: &Uid) -> Result<(), String> { + fn send_price_info(&mut self, target: &Uid, item_name: &str) -> Result<(), String> { let player_name = self .find_name(target) .ok_or("Failed to find player name")? .to_string(); + let mut found = false; - self.client.send_command( - "tell".to_string(), - vec![ - player_name.clone(), - format!("Buy prices: {:?}", self.buy_prices), - ], - ); - self.client.send_command( - "tell".to_string(), - vec![player_name, format!("Sell prices: {:?}", self.sell_prices)], - ); + for (item_id, price) in &self.buy_prices { + if item_id.contains(item_name) { + let short_id = item_id.splitn(3, '.').last().unwrap_or_default(); + + log::debug!("Sending price info on {short_id} to {player_name}"); + + self.client.send_command( + "tell".to_string(), + vec![ + player_name.clone(), + format!("Buying {short_id} for {price} coins."), + ], + ); + + found = true; + } + } + + for (item_id, price) in &self.sell_prices { + if item_id.contains(item_name) { + let short_id = item_id.splitn(3, '.').last().unwrap_or_default(); + + log::debug!("Sending price info on {short_id} to {player_name}"); + + self.client.send_command( + "tell".to_string(), + vec![ + player_name.clone(), + format!("Selling {short_id} for {price} coins."), + ], + ); + + found = true; + } + } + + if !found { + log::debug!("Found no price for \"{item_name}\" for {player_name}"); + + self.client.send_command( + "tell".to_string(), + vec![ + player_name.clone(), + format!("I don't have a price for that item."), + ], + ); + } Ok(()) } @@ -535,34 +539,13 @@ impl Bot { fn find_name<'a>(&'a self, uid: &Uid) -> Option<&'a String> { self.client.player_list().iter().find_map(|(id, info)| { if id == uid { - if let Some(character_info) = &info.character { - return Some(&character_info.name); - } + return Some(&info.player_alias); } + None }) } - fn _find_uid<'a>(&'a self, name: &str) -> Option<&'a Uid> { - self.client.player_list().iter().find_map(|(id, info)| { - if info.player_alias == name { - Some(id) - } else { - None - } - }) - } - - fn _find_uuid(&self, name: &str) -> Option { - self.client.player_list().iter().find_map(|(_, info)| { - if info.player_alias == name { - Some(info.uuid.to_string()) - } else { - None - } - }) - } - fn handle_position_and_orientation(&mut self) -> Result<(), String> { let current_position = self.client.current::(); @@ -601,6 +584,26 @@ impl Bot { Ok(()) } + + fn _find_uid<'a>(&'a self, name: &str) -> Option<&'a Uid> { + self.client.player_list().iter().find_map(|(id, info)| { + if info.player_alias == name { + Some(id) + } else { + None + } + }) + } + + fn _find_uuid(&self, name: &str) -> Option { + self.client.player_list().iter().find_map(|(_, info)| { + if info.player_alias == name { + Some(info.uuid.to_string()) + } else { + None + } + }) + } } enum TradeMode { diff --git a/src/main.rs b/src/main.rs index bb81467..f12afdc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,11 +9,11 @@ use serde::{Deserialize, Serialize}; pub struct Secrets { pub username: String, pub password: String, + pub character: String, } #[derive(Serialize, Deserialize)] struct Config { - pub character: String, pub buy_prices: HashMap, pub sell_prices: HashMap, pub position: [f32; 3], @@ -42,7 +42,7 @@ fn main() { let mut bot = Bot::new( &secrets.username, &secrets.password, - &config.character, + &secrets.character, config.buy_prices, config.sell_prices, config.position,