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
# 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

View File

@ -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

View File

@ -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}");

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};
#[derive(Deserialize)]
/// Non-sensitive configuration values.
///
/// See the [module-level documentation](index.html) for more information.
pub struct Config {
pub game_server: Option<String>,
pub auth_server: Option<String>,
@ -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<String>,
}
pub struct PriceList {
pub simple: HashMap<ItemDefinitionIdOwned, u32>,
pub modular: Vec<ModularItemPrice>,
}
/// Buy or sell prices for items.
pub struct PriceList(pub HashMap<ItemDefinitionIdOwned, u32>);
impl<'de> Deserialize<'de> for PriceList {
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;
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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
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::<String>()? {
match key.as_str() {
"simple" => {
let simple_prices_with_item_string =
map.next_value::<HashMap<String, u32>>()?;
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::<String, u32>()? {
let item_id = match key.splitn(3, '|').collect::<Vec<&str>>().as_slice() {
[material, primary, secondary] => {
let material = material.parse::<Material>().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);
}
"modular" => {
modular = Some(map.next_value()?);
}
_ => {
return Err(serde::de::Error::unknown_field(
&key,
&["simple", "modular"],
));
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],
}
}
[simple] => {
let mut simple = simple.to_string();
simple.insert_str(0, "common.items.");
ItemDefinitionIdOwned::Simple(simple)
}
_ => 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<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"))?,
})
Ok(PriceList(prices))
}
}