Change config again; Update docs

This commit is contained in:
Jeff 2024-07-16 20:15:14 -04:00
parent c628e22eff
commit 2a2a295cc2
4 changed files with 108 additions and 226 deletions

View File

@ -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 # 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. # explore the item definition IDs. Then just leave off the "common.items." part in this file.
[buy_prices.simple] [buy_prices]
"food.cheese" = 50 "food.cheese" = 50
[sell_prices.simple] [sell_prices]
"consumable.potion_minor" = 150 "consumable.potion_minor" = 150
[sell_prices.modular] # Modular weapons listed by that components: "material|primary|secondary".
material = "mineral.ingot.orichalcum" "iron|sword.greatsword|sword.long" = 1_000
primary = "sword.greatsword"
seconday = "sword.long"
price = 45_000
``` ```
### Running ### Running

View File

@ -1,20 +1,10 @@
position = [17720.0, 14951.0, 237.0] position = [17720.0, 14951.0, 237.0]
orientation = 0 orientation = 0
[buy_prices.simple] [buy_prices]
"food.cheese" = 50 "food.cheese" = 50
"iron|sword.greatsword|sword.long" = 1_000
[[buy_prices.modular]] [sell_prices]
material = "Iron"
primary = "sword.greatsword"
secondary = "sword.long"
price = 1000
[sell_prices.simple]
"consumable.potion_minor" = 150 "consumable.potion_minor" = 150
"iron|sword.greatsword|sword.long" = 1_000
[[sell_prices.modular]]
material = "Iron"
primary = "sword.greatsword"
secondary = "sword.long"
price = 1000

View File

@ -32,8 +32,8 @@ use veloren_common_net::sync::WorldSyncExt;
use crate::config::PriceList; use crate::config::PriceList;
const COINS: &str = "common.items.utility.coins"; const COINS: ItemDefinitionId =
const COINS_ID: ItemDefinitionId = ItemDefinitionId::Simple(Cow::Borrowed(COINS)); ItemDefinitionId::Simple(Cow::Borrowed("common.items.utility.coins"));
/// An active connection to the Veloren server that will attempt to run every time the `tick` /// An active connection to the Veloren server that will attempt to run every time the `tick`
/// function is called. /// function is called.
@ -550,7 +550,7 @@ impl Bot {
inventories.get(them).ok_or("Failed to find inventory")?, 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_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); 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) { if let Some(item) = my_inventory.get(*slot_id) {
let item_id = item.item_definition_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; my_offered_coin_amount = *quantity as i32;
1 1
} else { } else {
self.sell_prices self.sell_prices
.simple .0
.get(&item_id) .get(&item_id)
.map(|int| *int as i32) .map(|int| *int as i32)
.unwrap_or_else(|| { .unwrap_or_else(|| {
self.buy_prices self.buy_prices
.simple .0
.get(&item_id) .get(&item_id)
.map(|int| 0 - *int as i32) .map(|int| 0 - *int as i32)
.unwrap_or(i32::MIN) .unwrap_or(i32::MIN)
@ -592,18 +592,18 @@ impl Bot {
if let Some(item) = their_inventory.get(*slot_id) { if let Some(item) = their_inventory.get(*slot_id) {
let item_id = item.item_definition_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; their_offered_coin_amount = *quantity as i32;
1 1
} else { } else {
self.buy_prices self.buy_prices
.simple .0
.get(&item_id) .get(&item_id)
.map(|int| *int as i32) .map(|int| *int as i32)
.unwrap_or_else(|| { .unwrap_or_else(|| {
self.sell_prices self.sell_prices
.simple .0
.get(&item_id) .get(&item_id)
.map(|int| 0 - *int as i32) .map(|int| 0 - *int as i32)
.unwrap_or(0) .unwrap_or(0)
@ -622,11 +622,11 @@ impl Bot {
let item = my_inventory.get(*slot_id).ok_or("Failed to get item")?; let item = my_inventory.get(*slot_id).ok_or("Failed to get item")?;
let item_id = item.item_definition_id(); let item_id = item.item_definition_id();
if item_id == COINS_ID { if item_id == COINS {
continue; 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)); 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 = their_inventory.get(*slot_id).ok_or("Failed to get item")?;
let item_id = item.item_definition_id(); let item_id = item.item_definition_id();
if item_id == COINS_ID { if item_id == COINS {
continue; 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)); their_item_to_remove = Some((slot_id, amount));
} }
} }
@ -748,7 +748,7 @@ impl Bot {
.to_string(); .to_string();
let mut found = false; 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()); let item_name = self.get_item_name(item_id.as_ref());
if item_name.to_lowercase().contains(&search_term) { if item_name.to_lowercase().contains(&search_term) {
@ -784,47 +784,7 @@ impl Bot {
} }
} }
for modular_item_listing in &self.buy_prices.modular { for (item_id, price) in &self.sell_prices {
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 {
let item_name = self.get_item_name(item_id.as_ref()); let item_name = self.get_item_name(item_id.as_ref());
if item_name.to_lowercase().contains(&search_term) { 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 { if !found {
log::info!("Found no price for \"{original_search_term}\" for {player_name}"); log::info!("Found no price for \"{original_search_term}\" for {player_name}");

View File

@ -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}; use veloren_common::comp::item::{ItemDefinitionIdOwned, Material};
#[derive(Deserialize)] #[derive(Deserialize)]
/// Non-sensitive configuration values.
///
/// See the [module-level documentation](index.html) for more information.
pub struct Config { pub struct Config {
pub game_server: Option<String>, pub game_server: Option<String>,
pub auth_server: Option<String>, pub auth_server: Option<String>,
@ -14,6 +32,9 @@ pub struct Config {
} }
#[derive(Deserialize)] #[derive(Deserialize)]
/// Sensitive configuration values.
///
/// See the [module-level documentation](index.html) for more information.
pub struct Secrets { pub struct Secrets {
pub username: String, pub username: String,
pub password: String, pub password: String,
@ -21,10 +42,8 @@ pub struct Secrets {
pub admins: Vec<String>, pub admins: Vec<String>,
} }
pub struct PriceList { /// Buy or sell prices for items.
pub simple: HashMap<ItemDefinitionIdOwned, u32>, pub struct PriceList(pub HashMap<ItemDefinitionIdOwned, u32>);
pub modular: Vec<ModularItemPrice>,
}
impl<'de> Deserialize<'de> for PriceList { impl<'de> Deserialize<'de> for PriceList {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@ -35,128 +54,84 @@ impl<'de> Deserialize<'de> for PriceList {
} }
} }
impl IntoIterator for PriceList {
type Item = (ItemDefinitionIdOwned, u32);
type IntoIter = hash_map::IntoIter<ItemDefinitionIdOwned, u32>;
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; pub struct PriceListVisitor;
impl<'de> Visitor<'de> for PriceListVisitor { impl<'de> Visitor<'de> for PriceListVisitor {
type Value = PriceList; type Value = PriceList;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 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<A>(self, mut map: A) -> Result<Self::Value, A::Error> fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where where
A: serde::de::MapAccess<'de>, A: serde::de::MapAccess<'de>,
{ {
let mut simple = None; let mut prices = HashMap::new();
let mut modular = None;
while let Some(key) = map.next_key::<String>()? { while let Some((key, value)) = map.next_entry::<String, u32>()? {
match key.as_str() { let item_id = match key.splitn(3, '|').collect::<Vec<&str>>().as_slice() {
"simple" => { [material, primary, secondary] => {
let simple_prices_with_item_string = let material = material.parse::<Material>().map_err(de::Error::custom)?;
map.next_value::<HashMap<String, u32>>()?; let mut primary = primary.to_string();
let simple_prices_with_item_id = simple_prices_with_item_string let mut secondary = secondary.to_string();
.into_iter()
.map(|(mut key, value)| {
key.insert_str(0, "common.items.");
(ItemDefinitionIdOwned::Simple(key), value) primary.insert_str(0, "common.items.modular.weapon.primary.");
}) secondary.insert_str(0, "common.items.modular.weapon.secondar.");
.collect();
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" => { [simple] => {
modular = Some(map.next_value()?); let mut simple = simple.to_string();
simple.insert_str(0, "common.items.");
ItemDefinitionIdOwned::Simple(simple)
} }
_ => { _ => return Err(de::Error::custom("Invalid key format")),
return Err(serde::de::Error::unknown_field( };
&key,
&["simple", "modular"], prices.insert(item_id, value);
));
}
}
} }
Ok(PriceList { Ok(PriceList(prices))
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<D>(deserializer: D) -> Result<Self, D::Error>
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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
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::<String>()? {
match key.as_str() {
"material" => {
material = Some(map.next_value()?);
}
"primary" => {
let mut primary_string = map.next_value::<String>()?;
primary_string.insert_str(0, "common.items.modular.weapon.primary.");
primary = Some(ItemDefinitionIdOwned::Simple(primary_string));
}
"secondary" => {
let mut secondary_string = map.next_value::<String>()?;
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"))?,
})
} }
} }