Add features

This commit is contained in:
Jeff 2024-07-04 14:40:07 -04:00
parent 1abc5ac6f1
commit ea070e5bc5
2 changed files with 207 additions and 57 deletions

View File

@ -1,5 +1,5 @@
use std::{ use std::{
borrow::Borrow, borrow::{Borrow, BorrowMut},
collections::HashMap, collections::HashMap,
sync::{ sync::{
mpsc::{self, Receiver, Sender}, mpsc::{self, Receiver, Sender},
@ -11,19 +11,26 @@ use std::{
use serde::Serialize; use serde::Serialize;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use veloren_client::{addr::ConnectionArgs, Client, Event as VelorenEvent}; use veloren_client::{addr::ConnectionArgs, Client, Event as VelorenEvent, WorldExt};
use veloren_common::{ use veloren_common::{
clock::Clock, clock::Clock,
comp::{ comp::{
self,
character_state::CharacterStateEventEmitters,
invite::InviteKind, invite::InviteKind,
item::{self, ItemDefinitionId, ItemDefinitionIdOwned, ItemDesc}, item::{self, ItemDefinitionId, ItemDefinitionIdOwned, ItemDesc},
ChatType, ControllerInputs, Item, CharacterState, ChatType, Controller, ControllerInputs, InputKind, Item, Pos,
}, },
event::{EmitExt, EventBus},
outcome::Outcome,
trade::{PendingTrade, TradeAction, TradePhase}, trade::{PendingTrade, TradeAction, TradePhase},
uid::Uid, uid::Uid,
ViewDistances, ViewDistances,
}; };
use veloren_common_net::{msg::InviteAnswer, sync::WorldSyncExt}; use veloren_common_net::{
msg::{InviteAnswer, Notification},
sync::WorldSyncExt,
};
const COINS: &str = "common.items.utility.coins"; const COINS: &str = "common.items.utility.coins";
@ -35,6 +42,7 @@ enum TradeMode {
pub struct Bot { pub struct Bot {
username: String, username: String,
position: [f32; 3],
client: Client, client: Client,
clock: Clock, clock: Clock,
buy_prices: HashMap<String, u32>, buy_prices: HashMap<String, u32>,
@ -49,17 +57,19 @@ impl Bot {
password: &str, password: &str,
buy_prices: HashMap<String, u32>, buy_prices: HashMap<String, u32>,
sell_prices: HashMap<String, u32>, sell_prices: HashMap<String, u32>,
position: [f32; 3],
) -> Result<Self, String> { ) -> Result<Self, String> {
let client = connect_to_veloren(&username, password)?; let client = connect_to_veloren(&username, password)?;
let clock = Clock::new(Duration::from_secs_f64(1.0 / 60.0)); let clock = Clock::new(Duration::from_secs_f64(1.0 / 60.0));
Ok(Bot { Ok(Bot {
username, username,
position,
client, client,
clock, clock,
buy_prices, buy_prices,
sell_prices, sell_prices,
last_action: Instant::from(0), last_action: Instant::now(),
trade_mode: TradeMode::Sell, trade_mode: TradeMode::Sell,
}) })
} }
@ -105,70 +115,128 @@ impl Bot {
self.handle_veloren_event(event)?; self.handle_veloren_event(event)?;
} }
if self.last_action.elapsed() > Duration::from_millis(100) { if self.last_action.elapsed() > Duration::from_millis(300) {
if let Some((_, trade, _)) = self.client.pending_trade() { if self.client.is_dead() {
match self.trade_mode { self.client.respawn();
TradeMode::Buy => self.handle_buying(trade)?, }
TradeMode::Take => self.handle_take(trade),
TradeMode::Sell => self.handle_selling(trade)?, if !self.client.is_lantern_enabled() {
self.client.enable_lantern();
}
if let Some(position) = self.client.position() {
if position != self.position.into() {
let entity = self.client.entity().clone();
let mut position_state = self.client.state_mut().ecs().write_storage::<Pos>();
position_state.insert(entity, Pos(self.position.into()));
} }
} }
if let Some((_, trade, _)) = self.client.pending_trade() {
match self.trade_mode {
TradeMode::Buy => self.handle_buy(trade.clone())?,
TradeMode::Take => self.handle_take(trade.clone()),
TradeMode::Sell => self.handle_sell(trade.clone())?,
}
}
self.last_action = Instant::now();
} }
self.client.cleanup();
self.clock.tick(); self.clock.tick();
Ok(()) Ok(())
} }
fn handle_veloren_event(&mut self, event: VelorenEvent) -> Result<(), String> { fn handle_veloren_event(&mut self, event: VelorenEvent) -> Result<(), String> {
if let VelorenEvent::Chat(message) = event { match event {
let content = message.content().as_plain().unwrap_or_default(); VelorenEvent::Chat(message) => {
let content = message.content().as_plain().unwrap_or_default();
if !content.starts_with(&self.username) { if !content.starts_with(&self.username) {
return Ok(()); return Ok(());
} }
match message.chat_type { match message.chat_type {
ChatType::Tell(sender_uid, _) | ChatType::Say(sender_uid) => { ChatType::Tell(sender_uid, _) | ChatType::Say(sender_uid) => {
if !self.client.is_trading() { if !self.client.is_trading() {
match content.trim_start_matches(&self.username).trim() { match content.trim_start_matches(&self.username).trim() {
"buy" => { "buy" => {
self.trade_mode = TradeMode::Buy; self.trade_mode = TradeMode::Buy;
self.client.send_invite(sender_uid, InviteKind::Trade); self.client.send_invite(sender_uid, InviteKind::Trade);
}
"sell" => {
self.trade_mode = TradeMode::Sell;
self.client.send_invite(sender_uid, InviteKind::Trade);
}
"take" => {
self.trade_mode = TradeMode::Take;
self.client.send_invite(sender_uid, InviteKind::Trade);
}
"prices" => {
let player_name = self
.find_name(&sender_uid)
.ok_or("Failed to find player name")?
.to_string();
self.client.send_command(
"tell".to_string(),
vec![
player_name.clone(),
format!("Buy prices: {:?}", self.buy_prices),
],
);
self.client.send_command(
"tell".to_string(),
vec![
player_name,
format!("Sell prices: {:?}", self.sell_prices),
],
);
}
_ => {}
} }
"sell" => {
self.trade_mode = TradeMode::Sell;
self.client.send_invite(sender_uid, InviteKind::Trade);
}
"take" => {
self.trade_mode = TradeMode::Take;
self.client.send_invite(sender_uid, InviteKind::Trade);
}
_ => {}
} }
} }
_ => {}
} }
_ => {}
} }
VelorenEvent::Outcome(Outcome::ProjectileHit {
pos,
body,
vel,
source,
target: Some(target),
}) => {
if let Some(uid) = self.client.uid() {
if uid == target {
self.client
.send_command("say".to_string(), vec!["Ouch!".to_string()])
}
}
}
_ => (),
} }
Ok(()) Ok(())
} }
fn handle_buying(&mut self, trade: &PendingTrade) -> Result<(), String> { fn handle_buy(&mut self, trade: PendingTrade) -> Result<(), String> {
let inventories = self.client.inventories(); let inventories = self.client.inventories();
let my_inventory = inventories.get(self.client.entity()).unwrap(); let my_inventory = inventories.get(self.client.entity()).unwrap();
let my_coins = my_inventory.get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple( let my_coins = my_inventory
"common.items.utility.coins".to_string(), .get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string()))
)); .ok_or("Failed to find coins".to_string())?;
let them = self let them = self
.client .client
.state() .state()
.ecs() .ecs()
.entity_from_uid(trade.parties[1]) .entity_from_uid(trade.parties[1])
.unwrap(); .ok_or("Failed to find player".to_string())?;
let their_inventory = inventories.get(them).unwrap(); let their_inventory = inventories
.get(them)
.ok_or("Failed to find inventory".to_string())?;
let their_offered_items_value = let their_offered_items_value =
(&trade.offers[1]) (&trade.offers[1])
.into_iter() .into_iter()
@ -193,9 +261,7 @@ impl Bot {
return None; return None;
}; };
if item.item_definition_id() if item.item_definition_id() == ItemDefinitionId::Simple(COINS.into()) {
== ItemDefinitionId::Simple("common.items.utility.coins".into())
{
Some(quantity) Some(quantity)
} else { } else {
None None
@ -204,20 +270,60 @@ impl Bot {
.unwrap_or(&0); .unwrap_or(&0);
let difference: i32 = their_offered_items_value as i32 - *my_offered_coins as i32; let difference: i32 = their_offered_items_value as i32 - *my_offered_coins as i32;
let mut my_items_to_remove = Vec::new();
for (item_id, quantity) in &trade.offers[0] {
let item = my_inventory
.get(item_id.clone())
.ok_or("Failed to find item".to_string())?;
if item.item_definition_id() != ItemDefinitionId::Simple(COINS.into()) {
my_items_to_remove.push((item_id.clone(), *quantity));
}
}
let mut their_items_to_remove = Vec::new();
for (item_id, quantity) in &trade.offers[1] {
let item = their_inventory
.get(item_id.clone())
.ok_or("Failed to find item".to_string())?;
if !self.buy_prices.contains_key(&item.persistence_item_id()) {
their_items_to_remove.push((item_id.clone(), *quantity));
}
}
drop(inventories); drop(inventories);
for (item, quantity) in my_items_to_remove {
self.client.perform_trade_action(TradeAction::RemoveItem {
item,
quantity,
ours: true,
});
}
for (item, quantity) in their_items_to_remove {
self.client.perform_trade_action(TradeAction::RemoveItem {
item,
quantity,
ours: false,
});
}
if difference == 0 { if difference == 0 {
self.client self.client
.perform_trade_action(TradeAction::Accept(trade.phase)); .perform_trade_action(TradeAction::Accept(trade.phase));
} else if difference.is_positive() { } else if difference.is_positive() {
self.client.perform_trade_action(TradeAction::AddItem { self.client.perform_trade_action(TradeAction::AddItem {
item: my_coins.unwrap(), item: my_coins,
quantity: difference as u32, quantity: difference as u32,
ours: true, ours: true,
}); });
} else if difference.is_negative() { } else if difference.is_negative() {
self.client.perform_trade_action(TradeAction::RemoveItem { self.client.perform_trade_action(TradeAction::RemoveItem {
item: my_coins.unwrap(), item: my_coins,
quantity: difference.abs() as u32, quantity: difference.abs() as u32,
ours: true, ours: true,
}); });
@ -226,7 +332,7 @@ impl Bot {
Ok(()) Ok(())
} }
fn handle_selling(&mut self, trade: &PendingTrade) -> Result<(), String> { fn handle_sell(&mut self, trade: PendingTrade) -> Result<(), String> {
let inventories = self.client.inventories(); let inventories = self.client.inventories();
let my_inventory = inventories.get(self.client.entity()).unwrap(); let my_inventory = inventories.get(self.client.entity()).unwrap();
let them = self let them = self
@ -234,18 +340,18 @@ impl Bot {
.state() .state()
.ecs() .ecs()
.entity_from_uid(trade.parties[1]) .entity_from_uid(trade.parties[1])
.unwrap(); .ok_or("Failed to find player".to_string())?;
let their_inventory = inventories.get(them).unwrap(); let their_inventory = inventories
let their_coins = their_inventory.get_slot_of_item_by_def_id( .get(them)
&ItemDefinitionIdOwned::Simple("common.items.utility.coins".to_string()), .ok_or("Failed to find inventory".to_string())?;
); let their_coins = their_inventory
.get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string()))
.ok_or("Failed to find coins")?;
let my_offered_items_value = let my_offered_items_value =
(&trade.offers[0]) (&trade.offers[0])
.into_iter() .into_iter()
.fold(0, |acc, (slot_id, quantity)| { .fold(0, |acc, (slot_id, quantity)| {
if let Some(item) = my_inventory.get(slot_id.clone()) { if let Some(item) = my_inventory.get(slot_id.clone()) {
println!("{}", item.persistence_item_id());
let item_value = self let item_value = self
.sell_prices .sell_prices
.get(&item.persistence_item_id()) .get(&item.persistence_item_id())
@ -276,20 +382,62 @@ impl Bot {
.unwrap_or(&0); .unwrap_or(&0);
let difference: i32 = my_offered_items_value as i32 - *their_offered_coins as i32; let difference: i32 = my_offered_items_value as i32 - *their_offered_coins as i32;
let mut their_items_to_remove = Vec::new();
for (item_id, quantity) in &trade.offers[1] {
let item = their_inventory
.get(item_id.clone())
.ok_or("Failed to find item".to_string())?;
if item.item_definition_id()
!= ItemDefinitionId::Simple("common.items.utility.coins".into())
{
their_items_to_remove.push((item_id.clone(), *quantity));
}
}
let mut my_items_to_remove = Vec::new();
for (item_id, quantity) in &trade.offers[0] {
let item = my_inventory
.get(item_id.clone())
.ok_or("Failed to find item".to_string())?;
if !self.sell_prices.contains_key(&item.persistence_item_id()) {
my_items_to_remove.push((item_id.clone(), *quantity));
}
}
drop(inventories); drop(inventories);
for (item, quantity) in their_items_to_remove {
self.client.perform_trade_action(TradeAction::RemoveItem {
item,
quantity,
ours: false,
});
}
for (item, quantity) in my_items_to_remove {
self.client.perform_trade_action(TradeAction::RemoveItem {
item,
quantity,
ours: true,
});
}
if difference == 0 { if difference == 0 {
self.client self.client
.perform_trade_action(TradeAction::Accept(trade.phase)); .perform_trade_action(TradeAction::Accept(trade.phase));
} else if difference.is_positive() { } else if difference.is_positive() {
self.client.perform_trade_action(TradeAction::AddItem { self.client.perform_trade_action(TradeAction::AddItem {
item: their_coins.unwrap(), item: their_coins,
quantity: difference as u32, quantity: difference as u32,
ours: false, ours: false,
}); });
} else if difference.is_negative() { } else if difference.is_negative() {
self.client.perform_trade_action(TradeAction::RemoveItem { self.client.perform_trade_action(TradeAction::RemoveItem {
item: their_coins.unwrap(), item: their_coins,
quantity: difference.abs() as u32, quantity: difference.abs() as u32,
ours: false, ours: false,
}); });
@ -298,7 +446,7 @@ impl Bot {
Ok(()) Ok(())
} }
fn handle_take(&self, trade: &PendingTrade) { fn handle_take(&mut self, trade: PendingTrade) {
if trade.offers[0].is_empty() && !trade.offers[1].is_empty() { if trade.offers[0].is_empty() && !trade.offers[1].is_empty() {
self.client self.client
.perform_trade_action(TradeAction::Accept(trade.phase)); .perform_trade_action(TradeAction::Accept(trade.phase));

View File

@ -18,6 +18,7 @@ struct Config {
pub password: String, pub password: String,
pub buy_prices: HashMap<String, u32>, pub buy_prices: HashMap<String, u32>,
pub sell_prices: HashMap<String, u32>, pub sell_prices: HashMap<String, u32>,
pub position: [f32; 3],
} }
impl Config { impl Config {
@ -43,12 +44,13 @@ fn main() {
&config.password, &config.password,
config.buy_prices, config.buy_prices,
config.sell_prices, config.sell_prices,
config.position,
) )
.expect("Failed to create bot"); .expect("Failed to create bot");
bot.select_character().expect("Failed to select character"); bot.select_character().expect("Failed to select character");
loop { loop {
bot.tick().expect("Failed to run bot"); bot.tick().inspect_err(|error| eprintln!("{error}"));
} }
} }