Change config again; Update docs
This commit is contained in:
parent
c628e22eff
commit
2a2a295cc2
11
README.md
11
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
|
# 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
|
||||||
|
@ -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
|
|
||||||
|
110
src/bot.rs
110
src/bot.rs
@ -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}");
|
||||||
|
|
||||||
|
195
src/config.rs
195
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};
|
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"))?,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user