Compare commits
2 Commits
0f2882cc69
...
5f03f6bac3
Author | SHA1 | Date | |
---|---|---|---|
5f03f6bac3 | |||
d668bc533a |
163
src/bot.rs
163
src/bot.rs
@ -23,6 +23,7 @@ use veloren_common::{
|
|||||||
comp::{
|
comp::{
|
||||||
invite::InviteKind,
|
invite::InviteKind,
|
||||||
item::{ItemDefinitionId, ItemDefinitionIdOwned, ItemDesc, ItemI18n, MaterialStatManifest},
|
item::{ItemDefinitionId, ItemDefinitionIdOwned, ItemDesc, ItemI18n, MaterialStatManifest},
|
||||||
|
slot::InvSlotId,
|
||||||
tool::AbilityMap,
|
tool::AbilityMap,
|
||||||
ChatType, ControllerInputs, Item, Ori, Pos,
|
ChatType, ControllerInputs, Item, Ori, Pos,
|
||||||
},
|
},
|
||||||
@ -59,7 +60,7 @@ pub struct Bot {
|
|||||||
sell_prices: HashMap<String, u32>,
|
sell_prices: HashMap<String, u32>,
|
||||||
trade_mode: TradeMode,
|
trade_mode: TradeMode,
|
||||||
|
|
||||||
previous_offer: Option<(HashMap<String, u32>, HashMap<String, u32>)>,
|
previous_offer: Option<(HashMap<InvSlotId, u32>, HashMap<InvSlotId, u32>)>,
|
||||||
last_trade_action: Instant,
|
last_trade_action: Instant,
|
||||||
last_announcement: Instant,
|
last_announcement: Instant,
|
||||||
last_ouch: Instant,
|
last_ouch: Instant,
|
||||||
@ -108,7 +109,7 @@ impl Bot {
|
|||||||
log::info!("Selecting a character");
|
log::info!("Selecting a character");
|
||||||
|
|
||||||
// This loop waits and retries requesting the character in the case that the character has
|
// This loop waits and retries requesting the character in the case that the character has
|
||||||
// logged out to recently.
|
// logged out too recently.
|
||||||
while client.position().is_none() {
|
while client.position().is_none() {
|
||||||
client.request_character(
|
client.request_character(
|
||||||
character_id,
|
character_id,
|
||||||
@ -504,82 +505,38 @@ impl Bot {
|
|||||||
.which_party(self.client.uid().ok_or("Failed to get uid")?)
|
.which_party(self.client.uid().ok_or("Failed to get uid")?)
|
||||||
.ok_or("Failed to get offer index")?;
|
.ok_or("Failed to get offer index")?;
|
||||||
let their_offer_index = if my_offer_index == 0 { 1 } else { 0 };
|
let their_offer_index = if my_offer_index == 0 { 1 } else { 0 };
|
||||||
let (my_offer, their_offer, them) = {
|
let (my_offer, their_offer) = {
|
||||||
(
|
(
|
||||||
&trade.offers[my_offer_index],
|
&trade.offers[my_offer_index],
|
||||||
&trade.offers[their_offer_index],
|
&trade.offers[their_offer_index],
|
||||||
self.client
|
|
||||||
.state()
|
|
||||||
.ecs()
|
|
||||||
.entity_from_uid(trade.parties[their_offer_index])
|
|
||||||
.ok_or("Failed to find player".to_string())?,
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let inventories = self.client.inventories();
|
|
||||||
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 item_offers = {
|
|
||||||
let mut their_items = HashMap::with_capacity(their_offer.len());
|
|
||||||
|
|
||||||
for (slot_id, quantity) in their_offer {
|
|
||||||
if let Some(item) = their_inventory.get(*slot_id) {
|
|
||||||
their_items.insert(item.persistence_item_id(), *quantity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut my_items = HashMap::with_capacity(my_offer.len());
|
|
||||||
|
|
||||||
for (slot_id, quantity) in my_offer {
|
|
||||||
if let Some(item) = my_inventory.get(*slot_id) {
|
|
||||||
my_items.insert(item.persistence_item_id(), *quantity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(my_items, their_items)
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the trade hasn't changed, do nothing to avoid spamming the server.
|
// If the trade hasn't changed, do nothing to avoid spamming the server.
|
||||||
if let Some(previous) = &self.previous_offer {
|
if let Some(previous) = &self.previous_offer {
|
||||||
if previous == &item_offers {
|
if (&previous.0, &previous.1) == (my_offer, their_offer) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let get_their_coins = their_inventory
|
let inventories = self.client.inventories();
|
||||||
.get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string()));
|
let me = self.client.entity();
|
||||||
let (mut their_offered_coin_amount, mut my_offered_coin_amount) = (0, 0);
|
let them = self
|
||||||
let their_offered_items_value =
|
.client
|
||||||
their_offer
|
.state()
|
||||||
.into_iter()
|
.ecs()
|
||||||
.fold(0, |acc: i32, (slot_id, quantity)| {
|
.entity_from_uid(trade.parties[their_offer_index])
|
||||||
if let Some(item) = their_inventory.get(*slot_id) {
|
.ok_or("Failed to find player".to_string())?;
|
||||||
let item_id = item.persistence_item_id();
|
let (my_inventory, their_inventory) = (
|
||||||
|
inventories.get(me).ok_or("Failed to find inventory")?,
|
||||||
|
inventories.get(them).ok_or("Failed to find inventory")?,
|
||||||
|
);
|
||||||
|
|
||||||
let item_value = if item_id == COINS {
|
let coins = ItemDefinitionIdOwned::Simple(COINS.to_string());
|
||||||
their_offered_coin_amount = *quantity as i32;
|
let get_my_coins = my_inventory.get_slot_of_item_by_def_id(&coins);
|
||||||
|
let get_their_coins = their_inventory.get_slot_of_item_by_def_id(&coins);
|
||||||
|
|
||||||
1
|
let (mut my_offered_coin_amount, mut their_offered_coin_amount) = (0, 0);
|
||||||
} else {
|
|
||||||
self.buy_prices
|
|
||||||
.get(&item_id)
|
|
||||||
.map(|int| *int as i32)
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
self.sell_prices
|
|
||||||
.get(&item_id)
|
|
||||||
.map(|int| 0 - *int as i32)
|
|
||||||
.unwrap_or(0)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
acc.saturating_add(item_value.saturating_mul(*quantity as i32))
|
|
||||||
} else {
|
|
||||||
acc
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let my_offered_items_value =
|
let my_offered_items_value =
|
||||||
my_offer
|
my_offer
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -603,6 +560,34 @@ impl Bot {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
acc.saturating_add(item_value.saturating_mul(*quantity as i32))
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let their_offered_items_value =
|
||||||
|
their_offer
|
||||||
|
.into_iter()
|
||||||
|
.fold(0, |acc: i32, (slot_id, quantity)| {
|
||||||
|
if let Some(item) = their_inventory.get(*slot_id) {
|
||||||
|
let item_id = item.persistence_item_id();
|
||||||
|
|
||||||
|
let item_value = if item_id == COINS {
|
||||||
|
their_offered_coin_amount = *quantity as i32;
|
||||||
|
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
self.buy_prices
|
||||||
|
.get(&item_id)
|
||||||
|
.map(|int| *int as i32)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
self.sell_prices
|
||||||
|
.get(&item_id)
|
||||||
|
.map(|int| 0 - *int as i32)
|
||||||
|
.unwrap_or(0)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
acc.saturating_add(item_value.saturating_mul(*quantity as i32))
|
acc.saturating_add(item_value.saturating_mul(*quantity as i32))
|
||||||
} else {
|
} else {
|
||||||
acc
|
acc
|
||||||
@ -641,6 +626,10 @@ impl Bot {
|
|||||||
|
|
||||||
drop(inventories);
|
drop(inventories);
|
||||||
|
|
||||||
|
// Up until now there may have been an error, so we only update the previous offer now.
|
||||||
|
// The trade action is infallible from here.
|
||||||
|
self.previous_offer = Some((my_offer.clone(), their_offer.clone()));
|
||||||
|
|
||||||
// Before running any actual trade logic, remove items that are not for sale or not being
|
// Before running any actual trade logic, remove items that are not for sale or not being
|
||||||
// purchased. End this trade action if an item was removed.
|
// purchased. End this trade action if an item was removed.
|
||||||
|
|
||||||
@ -667,16 +656,12 @@ impl Bot {
|
|||||||
let difference = their_offered_items_value - my_offered_items_value;
|
let difference = their_offered_items_value - my_offered_items_value;
|
||||||
|
|
||||||
// The if/else statements below implement the bot's main feature: buying, selling and
|
// The if/else statements below implement the bot's main feature: buying, selling and
|
||||||
// trading items according to the values set in the configuration file. In the case that
|
// trading items according to the values set in the configuration file. Coins are used to
|
||||||
// either the bot or the other player does not have any coins, the bot will not send an
|
// balance the value of the trade. In the case that we try to add more coins than are
|
||||||
// error and the trade will remain unbalanced. In the case that we try to add more coins
|
// available, the server will correct it by adding all of the available coins.
|
||||||
// than are available, the server will just add all of the available coins and the trade
|
|
||||||
// will remain unbalanced.
|
|
||||||
|
|
||||||
// If the trade is balanced
|
// If the trade is balanced
|
||||||
if difference == 0 {
|
if difference == 0 {
|
||||||
self.previous_offer = Some(item_offers);
|
|
||||||
|
|
||||||
// Accept
|
// Accept
|
||||||
self.client
|
self.client
|
||||||
.perform_trade_action(TradeAction::Accept(trade.phase));
|
.perform_trade_action(TradeAction::Accept(trade.phase));
|
||||||
@ -729,7 +714,12 @@ impl Bot {
|
|||||||
|
|
||||||
/// Attempts to find an item based on a search term and sends the price info to the target
|
/// Attempts to find an item based on a search term and sends the price info to the target
|
||||||
/// player.
|
/// player.
|
||||||
|
///
|
||||||
|
/// The search is case-insensitive. It searches both the item name then, if the search term is
|
||||||
|
/// not found, it searches the item's ID as written in the configuration file.
|
||||||
fn send_price_info(&mut self, target: &Uid, search_term: &str) -> Result<(), String> {
|
fn send_price_info(&mut self, target: &Uid, search_term: &str) -> Result<(), String> {
|
||||||
|
let original_search_term = search_term;
|
||||||
|
let search_term = search_term.to_lowercase();
|
||||||
let player_name = self
|
let player_name = self
|
||||||
.find_player_alias(target)
|
.find_player_alias(target)
|
||||||
.ok_or("Failed to find player name")?
|
.ok_or("Failed to find player name")?
|
||||||
@ -739,23 +729,19 @@ impl Bot {
|
|||||||
for (item_id, price) in &self.buy_prices {
|
for (item_id, price) in &self.buy_prices {
|
||||||
let item_name = self.get_item_name(item_id);
|
let item_name = self.get_item_name(item_id);
|
||||||
|
|
||||||
if item_name.contains(search_term) {
|
if item_name.to_lowercase().contains(&search_term) {
|
||||||
log::info!("Sending price info on {item_name} to {player_name}");
|
log::info!("Sending price info on {item_name} to {player_name}");
|
||||||
|
|
||||||
self.client.send_command(
|
self.client.send_command(
|
||||||
"tell".to_string(),
|
"tell".to_string(),
|
||||||
vec![
|
vec![
|
||||||
player_name.clone(),
|
player_name.clone(),
|
||||||
format!("Selling {item_name} for {price} coins."),
|
format!("Buying {item_name} for {price} coins."),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
found = true;
|
found = true;
|
||||||
|
} else if item_id.contains(&search_term) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if item_id.contains(search_term) {
|
|
||||||
let short_id = item_id.splitn(3, '.').last().unwrap_or_default();
|
let short_id = item_id.splitn(3, '.').last().unwrap_or_default();
|
||||||
|
|
||||||
log::info!("Sending price info on {short_id} to {player_name}");
|
log::info!("Sending price info on {short_id} to {player_name}");
|
||||||
@ -775,7 +761,7 @@ impl Bot {
|
|||||||
for (item_id, price) in &self.sell_prices {
|
for (item_id, price) in &self.sell_prices {
|
||||||
let item_name = self.get_item_name(item_id);
|
let item_name = self.get_item_name(item_id);
|
||||||
|
|
||||||
if item_name.contains(search_term) {
|
if item_name.to_lowercase().contains(&search_term) {
|
||||||
log::info!("Sending price info on {item_name} to {player_name}");
|
log::info!("Sending price info on {item_name} to {player_name}");
|
||||||
|
|
||||||
self.client.send_command(
|
self.client.send_command(
|
||||||
@ -787,11 +773,7 @@ impl Bot {
|
|||||||
);
|
);
|
||||||
|
|
||||||
found = true;
|
found = true;
|
||||||
|
} else if item_id.contains(&search_term) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if item_id.contains(search_term) {
|
|
||||||
let short_id = item_id.splitn(3, '.').last().unwrap_or_default();
|
let short_id = item_id.splitn(3, '.').last().unwrap_or_default();
|
||||||
|
|
||||||
log::info!("Sending price info on {short_id} to {player_name}");
|
log::info!("Sending price info on {short_id} to {player_name}");
|
||||||
@ -809,11 +791,14 @@ impl Bot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
log::info!("Found no price for \"{search_term}\" for {player_name}");
|
log::info!("Found no price for \"{original_search_term}\" for {player_name}");
|
||||||
|
|
||||||
self.client.send_command(
|
self.client.send_command(
|
||||||
"tell".to_string(),
|
"tell".to_string(),
|
||||||
vec![player_name, format!("I don't have a price for that item.")],
|
vec![
|
||||||
|
player_name,
|
||||||
|
format!("I don't have a price for {original_search_term}."),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -866,10 +851,10 @@ impl Bot {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the name of an item from its item definition id.
|
/// Gets the name of an item from its id.
|
||||||
fn get_item_name(&self, item: &str) -> String {
|
fn get_item_name(&self, item_id: &str) -> String {
|
||||||
let item = Item::new_from_item_definition_id(
|
let item = Item::new_from_item_definition_id(
|
||||||
ItemDefinitionId::Simple(Cow::Borrowed(item)),
|
ItemDefinitionId::Simple(Cow::Borrowed(item_id)),
|
||||||
&self.ability_map,
|
&self.ability_map,
|
||||||
&self.material_manifest,
|
&self.material_manifest,
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user