From 2a2a295cc254eebe9bd94673a57572f13c576372 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 16 Jul 2024 20:15:14 -0400 Subject: [PATCH] Change config again; Update docs --- README.md | 11 +-- config/config.toml | 18 +---- src/bot.rs | 110 ++++--------------------- src/config.rs | 195 ++++++++++++++++++++------------------------- 4 files changed, 108 insertions(+), 226 deletions(-) diff --git a/README.md b/README.md index 34041df..199c2b8 100644 --- a/README.md +++ b/README.md @@ -101,17 +101,14 @@ announcement = "I love cheese! I am at {location}." # values are the price in coins. You may type in-game "/give_item common.items." and press Tab to # explore the item definition IDs. Then just leave off the "common.items." part in this file. -[buy_prices.simple] +[buy_prices] "food.cheese" = 50 -[sell_prices.simple] +[sell_prices] "consumable.potion_minor" = 150 -[sell_prices.modular] -material = "mineral.ingot.orichalcum" -primary = "sword.greatsword" -seconday = "sword.long" -price = 45_000 +# Modular weapons listed by that components: "material|primary|secondary". +"iron|sword.greatsword|sword.long" = 1_000 ``` ### Running diff --git a/config/config.toml b/config/config.toml index 7d0effb..20721b5 100644 --- a/config/config.toml +++ b/config/config.toml @@ -1,20 +1,10 @@ position = [17720.0, 14951.0, 237.0] orientation = 0 -[buy_prices.simple] +[buy_prices] "food.cheese" = 50 +"iron|sword.greatsword|sword.long" = 1_000 -[[buy_prices.modular]] -material = "Iron" -primary = "sword.greatsword" -secondary = "sword.long" -price = 1000 - -[sell_prices.simple] +[sell_prices] "consumable.potion_minor" = 150 - -[[sell_prices.modular]] -material = "Iron" -primary = "sword.greatsword" -secondary = "sword.long" -price = 1000 +"iron|sword.greatsword|sword.long" = 1_000 diff --git a/src/bot.rs b/src/bot.rs index cccd1e7..849bd7f 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -32,8 +32,8 @@ use veloren_common_net::sync::WorldSyncExt; use crate::config::PriceList; -const COINS: &str = "common.items.utility.coins"; -const COINS_ID: ItemDefinitionId = ItemDefinitionId::Simple(Cow::Borrowed(COINS)); +const COINS: ItemDefinitionId = + ItemDefinitionId::Simple(Cow::Borrowed("common.items.utility.coins")); /// An active connection to the Veloren server that will attempt to run every time the `tick` /// function is called. @@ -550,7 +550,7 @@ impl Bot { inventories.get(them).ok_or("Failed to find inventory")?, ); - let coins_owned = COINS_ID.to_owned(); + let coins_owned = COINS.to_owned(); let get_my_coins = my_inventory.get_slot_of_item_by_def_id(&coins_owned); let get_their_coins = their_inventory.get_slot_of_item_by_def_id(&coins_owned); @@ -562,18 +562,18 @@ impl Bot { if let Some(item) = my_inventory.get(*slot_id) { let item_id = item.item_definition_id(); - let item_value = if item_id == COINS_ID { + let item_value = if item_id == COINS { my_offered_coin_amount = *quantity as i32; 1 } else { self.sell_prices - .simple + .0 .get(&item_id) .map(|int| *int as i32) .unwrap_or_else(|| { self.buy_prices - .simple + .0 .get(&item_id) .map(|int| 0 - *int as i32) .unwrap_or(i32::MIN) @@ -592,18 +592,18 @@ impl Bot { if let Some(item) = their_inventory.get(*slot_id) { let item_id = item.item_definition_id(); - let item_value = if item_id == COINS_ID { + let item_value = if item_id == COINS { their_offered_coin_amount = *quantity as i32; 1 } else { self.buy_prices - .simple + .0 .get(&item_id) .map(|int| *int as i32) .unwrap_or_else(|| { self.sell_prices - .simple + .0 .get(&item_id) .map(|int| 0 - *int as i32) .unwrap_or(0) @@ -622,11 +622,11 @@ impl Bot { let item = my_inventory.get(*slot_id).ok_or("Failed to get item")?; let item_id = item.item_definition_id(); - if item_id == COINS_ID { + if item_id == COINS { continue; } - if !self.sell_prices.simple.contains_key(&item_id) { + if !self.sell_prices.0.contains_key(&item_id) { my_item_to_remove = Some((slot_id, amount)); } } @@ -637,11 +637,11 @@ impl Bot { let item = their_inventory.get(*slot_id).ok_or("Failed to get item")?; let item_id = item.item_definition_id(); - if item_id == COINS_ID { + if item_id == COINS { continue; } - if !self.buy_prices.simple.contains_key(&item_id) { + if !self.buy_prices.0.contains_key(&item_id) { their_item_to_remove = Some((slot_id, amount)); } } @@ -748,7 +748,7 @@ impl Bot { .to_string(); let mut found = false; - for (item_id, price) in &self.buy_prices.simple { + for (item_id, price) in &self.buy_prices { let item_name = self.get_item_name(item_id.as_ref()); if item_name.to_lowercase().contains(&search_term) { @@ -784,47 +784,7 @@ impl Bot { } } - for modular_item_listing in &self.buy_prices.modular { - let material = ItemDefinitionId::Simple(Cow::Borrowed( - modular_item_listing - .material - .asset_identifier() - .ok_or(format!( - "{:?} is not a valid material for modular crafted items", - modular_item_listing.material - ))?, - )); - let primary = modular_item_listing.primary.as_ref(); - let secondary = ItemDefinitionId::Compound { - // This unwrap is safe because the ItemDefinitionId is always Simple. - simple_base: primary.itemdef_id().unwrap(), - components: vec![material], - }; - let item_id = ItemDefinitionId::Modular { - pseudo_base: "veloren.core.pseudo_items.modular.tool", - components: vec![secondary], - }; - let item_name = self.get_item_name(item_id); - - if item_name.to_lowercase().contains(&search_term) { - log::info!("Sending price info on {item_name} to {player_name}"); - - self.client.send_command( - "tell".to_string(), - vec![ - player_name.clone(), - format!( - "Buying {item_name} for {} coins.", - modular_item_listing.price - ), - ], - ); - - found = true; - } - } - - for (item_id, price) in &self.sell_prices.simple { + for (item_id, price) in &self.sell_prices { let item_name = self.get_item_name(item_id.as_ref()); if item_name.to_lowercase().contains(&search_term) { @@ -860,46 +820,6 @@ impl Bot { } } - for modular_item_listing in &self.sell_prices.modular { - let material = ItemDefinitionId::Simple(Cow::Borrowed( - modular_item_listing - .material - .asset_identifier() - .ok_or(format!( - "{:?} is not a valid material for modular crafted items", - modular_item_listing.material - ))?, - )); - let primary = modular_item_listing.primary.as_ref(); - let secondary = ItemDefinitionId::Compound { - // This unwrap is safe because the ItemDefinitionId is always Simple. - simple_base: primary.itemdef_id().unwrap(), - components: vec![material], - }; - let item_id = ItemDefinitionId::Modular { - pseudo_base: "veloren.core.pseudo_items.modular.tool", - components: vec![secondary], - }; - let item_name = self.get_item_name(item_id); - - if item_name.to_lowercase().contains(&search_term) { - log::info!("Sending price info on {item_name} to {player_name}"); - - self.client.send_command( - "tell".to_string(), - vec![ - player_name.clone(), - format!( - "Selling {item_name} for {} coins.", - modular_item_listing.price - ), - ], - ); - - found = true; - } - } - if !found { log::info!("Found no price for \"{original_search_term}\" for {player_name}"); diff --git a/src/config.rs b/src/config.rs index fa2d12b..e263b34 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,8 +1,26 @@ -use hashbrown::HashMap; -use serde::{de::Visitor, Deserialize}; +/** +Configuration used to initiate the bot. + +The Config struct is used to store configuration values that are not sensitive. This includes the +price data for items, the game server to connect to, and the position and orientation of the bot. +The price lists have manual implementations for deserialization to allow turning shortened item +IDs into the full IDs used by the Veloren client. + +The Secrets struct is used to store sensitive information that should not be shared. This should +be read from a separate file that is not checked into version control. In production, use a secure +means of storing this information, such as the secret manager for Podman. +*/ +use hashbrown::{hash_map, HashMap}; +use serde::{ + de::{self, Visitor}, + Deserialize, +}; use veloren_common::comp::item::{ItemDefinitionIdOwned, Material}; #[derive(Deserialize)] +/// Non-sensitive configuration values. +/// +/// See the [module-level documentation](index.html) for more information. pub struct Config { pub game_server: Option, pub auth_server: Option, @@ -14,6 +32,9 @@ pub struct Config { } #[derive(Deserialize)] +/// Sensitive configuration values. +/// +/// See the [module-level documentation](index.html) for more information. pub struct Secrets { pub username: String, pub password: String, @@ -21,10 +42,8 @@ pub struct Secrets { pub admins: Vec, } -pub struct PriceList { - pub simple: HashMap, - pub modular: Vec, -} +/// Buy or sell prices for items. +pub struct PriceList(pub HashMap); impl<'de> Deserialize<'de> for PriceList { fn deserialize(deserializer: D) -> Result @@ -35,128 +54,84 @@ impl<'de> Deserialize<'de> for PriceList { } } +impl IntoIterator for PriceList { + type Item = (ItemDefinitionIdOwned, u32); + type IntoIter = hash_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a PriceList { + type Item = (&'a ItemDefinitionIdOwned, &'a u32); + type IntoIter = hash_map::Iter<'a, ItemDefinitionIdOwned, u32>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + pub struct PriceListVisitor; impl<'de> Visitor<'de> for PriceListVisitor { type Value = PriceList; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a map with simple and/or modular keys") + formatter.write_str("a map with simple and/or modular keys: Material|primary|secondary") } fn visit_map(self, mut map: A) -> Result where A: serde::de::MapAccess<'de>, { - let mut simple = None; - let mut modular = None; + let mut prices = HashMap::new(); - while let Some(key) = map.next_key::()? { - match key.as_str() { - "simple" => { - let simple_prices_with_item_string = - map.next_value::>()?; - let simple_prices_with_item_id = simple_prices_with_item_string - .into_iter() - .map(|(mut key, value)| { - key.insert_str(0, "common.items."); + while let Some((key, value)) = map.next_entry::()? { + let item_id = match key.splitn(3, '|').collect::>().as_slice() { + [material, primary, secondary] => { + let material = material.parse::().map_err(de::Error::custom)?; + let mut primary = primary.to_string(); + let mut secondary = secondary.to_string(); - (ItemDefinitionIdOwned::Simple(key), value) - }) - .collect(); + primary.insert_str(0, "common.items.modular.weapon.primary."); + secondary.insert_str(0, "common.items.modular.weapon.secondar."); - simple = Some(simple_prices_with_item_id); + let material = ItemDefinitionIdOwned::Simple( + material + .asset_identifier() + .ok_or_else(|| { + de::Error::custom(format!( + "{:?} is not a valid material for modular crafted items", + material + )) + })? + .to_string(), + ); + let secondary = ItemDefinitionIdOwned::Compound { + // This unwrap is safe because the ItemDefinitionId is always Simple. + simple_base: primary, + components: vec![material], + }; + + ItemDefinitionIdOwned::Modular { + pseudo_base: "veloren.core.pseudo_items.modular.tool".to_string(), + components: vec![secondary], + } } - "modular" => { - modular = Some(map.next_value()?); + [simple] => { + let mut simple = simple.to_string(); + + simple.insert_str(0, "common.items."); + + ItemDefinitionIdOwned::Simple(simple) } - _ => { - return Err(serde::de::Error::unknown_field( - &key, - &["simple", "modular"], - )); - } - } + _ => return Err(de::Error::custom("Invalid key format")), + }; + + prices.insert(item_id, value); } - Ok(PriceList { - simple: simple.ok_or_else(|| serde::de::Error::missing_field("simple"))?, - modular: modular.ok_or_else(|| serde::de::Error::missing_field("modular"))?, - }) - } -} - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct ModularItemPrice { - pub material: Material, - pub primary: ItemDefinitionIdOwned, - pub secondary: ItemDefinitionIdOwned, - pub price: u32, -} - -impl<'de> Deserialize<'de> for ModularItemPrice { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_map(ModularPriceVisitor) - } -} - -struct ModularPriceVisitor; - -impl<'de> Visitor<'de> for ModularPriceVisitor { - type Value = ModularItemPrice; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a map with material, primary, secondary and price keys") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - let mut material = None; - let mut primary = None; - let mut secondary = None; - let mut price = None; - - while let Some(key) = map.next_key::()? { - match key.as_str() { - "material" => { - material = Some(map.next_value()?); - } - "primary" => { - let mut primary_string = map.next_value::()?; - - primary_string.insert_str(0, "common.items.modular.weapon.primary."); - - primary = Some(ItemDefinitionIdOwned::Simple(primary_string)); - } - "secondary" => { - let mut secondary_string = map.next_value::()?; - - secondary_string.insert_str(0, "common.items.modular.weapon.secondary."); - - secondary = Some(ItemDefinitionIdOwned::Simple(secondary_string)); - } - "price" => { - price = Some(map.next_value()?); - } - _ => { - return Err(serde::de::Error::unknown_field( - &key, - &["material", "primary", "secondary", "price"], - )); - } - } - } - - Ok(ModularItemPrice { - material: material.ok_or_else(|| serde::de::Error::missing_field("material"))?, - primary: primary.ok_or_else(|| serde::de::Error::missing_field("primary"))?, - secondary: secondary.ok_or_else(|| serde::de::Error::missing_field("secondary"))?, - price: price.ok_or_else(|| serde::de::Error::missing_field("price"))?, - }) + Ok(PriceList(prices)) } }