Overhaul trading method
This commit is contained in:
parent
3f1d08777c
commit
b86221e58f
102
Cargo.lock
generated
102
Cargo.lock
generated
@ -60,6 +60,55 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "approx"
|
name = "approx"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -357,6 +406,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@ -656,6 +711,29 @@ dependencies = [
|
|||||||
"syn 2.0.66",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_filter"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"env_filter",
|
||||||
|
"humantime",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -1044,6 +1122,12 @@ version = "1.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@ -1199,6 +1283,12 @@ version = "2.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
@ -1321,9 +1411,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.21"
|
version = "0.4.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru-cache"
|
name = "lru-cache"
|
||||||
@ -2700,6 +2790,8 @@ dependencies = [
|
|||||||
name = "trade-bot"
|
name = "trade-bot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
@ -2790,6 +2882,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -11,6 +11,8 @@ veloren-client = { git = "https://gitlab.com/veloren/veloren", branch = "master"
|
|||||||
veloren-world = { git = "https://gitlab.com/veloren/veloren", branch = "master" }
|
veloren-world = { git = "https://gitlab.com/veloren/veloren", branch = "master" }
|
||||||
toml = "0.8.14"
|
toml = "0.8.14"
|
||||||
serde = { version = "1.0.203", features = ["derive"] }
|
serde = { version = "1.0.203", features = ["derive"] }
|
||||||
|
log = "0.4.22"
|
||||||
|
env_logger = "0.11.3"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
specs = { git = "https://github.com/amethyst/specs.git", rev = "4e2da1df29ee840baa9b936593c45592b7c9ae27" }
|
specs = { git = "https://github.com/amethyst/specs.git", rev = "4e2da1df29ee840baa9b936593c45592b7c9ae27" }
|
||||||
|
503
src/bot.rs
503
src/bot.rs
@ -1,47 +1,29 @@
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::{Borrow, BorrowMut},
|
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{
|
sync::Arc,
|
||||||
mpsc::{self, Receiver, Sender},
|
time::{Duration, Instant},
|
||||||
Arc, Mutex,
|
|
||||||
},
|
|
||||||
thread,
|
|
||||||
time::{self, Duration, Instant, UNIX_EPOCH},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::Serialize;
|
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use veloren_client::{addr::ConnectionArgs, Client, Event as VelorenEvent, WorldExt};
|
use veloren_client::{addr::ConnectionArgs, Client, Event as VelorenEvent, WorldExt};
|
||||||
use veloren_common::{
|
use veloren_common::{
|
||||||
clock::Clock,
|
clock::Clock,
|
||||||
comp::{
|
comp::{invite::InviteKind, item::ItemDefinitionIdOwned, ChatType, ControllerInputs, Pos},
|
||||||
self,
|
|
||||||
character_state::CharacterStateEventEmitters,
|
|
||||||
invite::InviteKind,
|
|
||||||
item::{self, ItemDefinitionId, ItemDefinitionIdOwned, ItemDesc},
|
|
||||||
CharacterState, ChatType, Controller, ControllerInputs, InputKind, Item, Pos,
|
|
||||||
},
|
|
||||||
event::{EmitExt, EventBus},
|
|
||||||
outcome::Outcome,
|
outcome::Outcome,
|
||||||
trade::{PendingTrade, TradeAction, TradePhase},
|
trade::{PendingTrade, TradeAction},
|
||||||
uid::Uid,
|
uid::Uid,
|
||||||
ViewDistances,
|
ViewDistances,
|
||||||
};
|
};
|
||||||
use veloren_common_net::{
|
use veloren_common_net::sync::WorldSyncExt;
|
||||||
msg::{InviteAnswer, Notification},
|
|
||||||
sync::WorldSyncExt,
|
|
||||||
};
|
|
||||||
|
|
||||||
const COINS: &str = "common.items.utility.coins";
|
const COINS: &str = "common.items.utility.coins";
|
||||||
|
|
||||||
enum TradeMode {
|
enum TradeMode {
|
||||||
Take,
|
Take,
|
||||||
Buy,
|
Trade,
|
||||||
Sell,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Bot {
|
pub struct Bot {
|
||||||
username: String,
|
|
||||||
position: [f32; 3],
|
position: [f32; 3],
|
||||||
client: Client,
|
client: Client,
|
||||||
clock: Clock,
|
clock: Clock,
|
||||||
@ -49,6 +31,7 @@ pub struct Bot {
|
|||||||
sell_prices: HashMap<String, u32>,
|
sell_prices: HashMap<String, u32>,
|
||||||
last_action: Instant,
|
last_action: Instant,
|
||||||
last_announcement: Instant,
|
last_announcement: Instant,
|
||||||
|
is_player_notified: bool,
|
||||||
trade_mode: TradeMode,
|
trade_mode: TradeMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,11 +43,12 @@ impl Bot {
|
|||||||
sell_prices: HashMap<String, u32>,
|
sell_prices: HashMap<String, u32>,
|
||||||
position: [f32; 3],
|
position: [f32; 3],
|
||||||
) -> Result<Self, String> {
|
) -> Result<Self, String> {
|
||||||
|
log::info!("Connecting to veloren");
|
||||||
|
|
||||||
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,
|
|
||||||
position,
|
position,
|
||||||
client,
|
client,
|
||||||
clock,
|
clock,
|
||||||
@ -72,11 +56,14 @@ impl Bot {
|
|||||||
sell_prices,
|
sell_prices,
|
||||||
last_action: Instant::now(),
|
last_action: Instant::now(),
|
||||||
last_announcement: Instant::now(),
|
last_announcement: Instant::now(),
|
||||||
trade_mode: TradeMode::Buy,
|
is_player_notified: false,
|
||||||
|
trade_mode: TradeMode::Trade,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_character(&mut self) -> Result<(), String> {
|
pub fn select_character(&mut self) -> Result<(), String> {
|
||||||
|
log::info!("Selecting a character");
|
||||||
|
|
||||||
self.client.load_character_list();
|
self.client.load_character_list();
|
||||||
|
|
||||||
while self.client.character_list().loading {
|
while self.client.character_list().loading {
|
||||||
@ -103,6 +90,7 @@ impl Bot {
|
|||||||
entity: 4,
|
entity: 4,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
self.client.sort_inventory();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -131,28 +119,33 @@ impl Bot {
|
|||||||
let entity = self.client.entity().clone();
|
let entity = self.client.entity().clone();
|
||||||
let mut position_state = self.client.state_mut().ecs().write_storage::<Pos>();
|
let mut position_state = self.client.state_mut().ecs().write_storage::<Pos>();
|
||||||
|
|
||||||
position_state.insert(entity, Pos(self.position.into()));
|
position_state
|
||||||
|
.insert(entity, Pos(self.position.into()))
|
||||||
|
.map_err(|error| error.to_string())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((_, trade, _)) = self.client.pending_trade() {
|
if let Some((_, trade, _)) = self.client.pending_trade() {
|
||||||
match self.trade_mode {
|
match self.trade_mode {
|
||||||
TradeMode::Buy => self.handle_buy(trade.clone())?,
|
TradeMode::Trade => self.handle_trade(trade.clone())?,
|
||||||
TradeMode::Take => self.handle_take(trade.clone()),
|
TradeMode::Take => self.handle_take(trade.clone()),
|
||||||
TradeMode::Sell => self.handle_sell(trade.clone())?,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.client.is_trading() {
|
||||||
|
self.trade_mode = TradeMode::Trade;
|
||||||
|
self.is_player_notified = false;
|
||||||
|
|
||||||
|
self.client.accept_invite();
|
||||||
|
}
|
||||||
|
|
||||||
self.last_action = Instant::now();
|
self.last_action = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.last_announcement.elapsed() > Duration::from_secs(600) {
|
if self.last_announcement.elapsed() > Duration::from_secs(600) {
|
||||||
self.client.send_command(
|
self.client.send_command(
|
||||||
"region".to_string(),
|
"region".to_string(),
|
||||||
vec![
|
vec!["I'm a bot. Trade with me or say 'prices' to see my offers.".to_string()],
|
||||||
"I'm a bot. Use /say or /tell to give commands: 'buy', 'sell' or 'prices'."
|
|
||||||
.to_string(),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
self.last_announcement = Instant::now();
|
self.last_announcement = Instant::now();
|
||||||
@ -170,44 +163,13 @@ impl Bot {
|
|||||||
|
|
||||||
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() {
|
match content.trim() {
|
||||||
match content.trim() {
|
"prices" => self.send_price_info(&sender_uid)?,
|
||||||
"buy" => {
|
"take" => {
|
||||||
self.trade_mode = TradeMode::Buy;
|
if !self.client.is_trading() {
|
||||||
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.trade_mode = TradeMode::Take;
|
||||||
self.client.send_invite(sender_uid, InviteKind::Trade);
|
self.client.send_invite(sender_uid, InviteKind::Trade);
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match content.trim() {
|
|
||||||
"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),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -217,11 +179,8 @@ impl Bot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
VelorenEvent::Outcome(Outcome::ProjectileHit {
|
VelorenEvent::Outcome(Outcome::ProjectileHit {
|
||||||
pos,
|
|
||||||
body,
|
|
||||||
vel,
|
|
||||||
source,
|
|
||||||
target: Some(target),
|
target: Some(target),
|
||||||
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if let Some(uid) = self.client.uid() {
|
if let Some(uid) = self.client.uid() {
|
||||||
if uid == target {
|
if uid == target {
|
||||||
@ -236,16 +195,20 @@ impl Bot {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_buy(&mut self, trade: PendingTrade) -> Result<(), String> {
|
fn handle_trade(&mut self, trade: PendingTrade) -> Result<(), String> {
|
||||||
let (my_offer, their_offer) = {
|
let my_offer_index = trade
|
||||||
let my_offer_index = trade
|
.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) = {
|
||||||
|
|
||||||
(
|
(
|
||||||
&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 inventories = self.client.inventories();
|
||||||
@ -253,225 +216,204 @@ impl Bot {
|
|||||||
let my_coins = my_inventory
|
let my_coins = my_inventory
|
||||||
.get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string()))
|
.get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string()))
|
||||||
.ok_or("Failed to find coins".to_string())?;
|
.ok_or("Failed to find coins".to_string())?;
|
||||||
let them = self
|
|
||||||
.client
|
|
||||||
.state()
|
|
||||||
.ecs()
|
|
||||||
.entity_from_uid(trade.parties[1])
|
|
||||||
.ok_or("Failed to find player".to_string())?;
|
|
||||||
let their_inventory = inventories
|
|
||||||
.get(them)
|
|
||||||
.ok_or("Failed to find inventory".to_string())?;
|
|
||||||
let their_offered_items_value =
|
|
||||||
their_offer.into_iter().fold(0, |acc, (slot_id, quantity)| {
|
|
||||||
if let Some(item) = their_inventory.get(slot_id.clone()) {
|
|
||||||
let item_value = self
|
|
||||||
.buy_prices
|
|
||||||
.get(&item.persistence_item_id())
|
|
||||||
.unwrap_or(&1);
|
|
||||||
|
|
||||||
acc + (item_value * quantity)
|
|
||||||
} else {
|
|
||||||
acc
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let my_offered_coins = my_offer
|
|
||||||
.into_iter()
|
|
||||||
.find_map(|(slot_id, quantity)| {
|
|
||||||
let item = if let Some(item) = my_inventory.get(slot_id.clone()) {
|
|
||||||
item
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
if item.item_definition_id() == ItemDefinitionId::Simple(COINS.into()) {
|
|
||||||
Some(quantity)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(&0);
|
|
||||||
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 my_offer {
|
|
||||||
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 their_offer {
|
|
||||||
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);
|
|
||||||
|
|
||||||
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 {
|
|
||||||
self.client
|
|
||||||
.perform_trade_action(TradeAction::Accept(trade.phase));
|
|
||||||
} else if difference.is_positive() {
|
|
||||||
self.client.perform_trade_action(TradeAction::AddItem {
|
|
||||||
item: my_coins,
|
|
||||||
quantity: difference as u32,
|
|
||||||
ours: true,
|
|
||||||
});
|
|
||||||
} else if difference.is_negative() {
|
|
||||||
self.client.perform_trade_action(TradeAction::RemoveItem {
|
|
||||||
item: my_coins,
|
|
||||||
quantity: difference.abs() as u32,
|
|
||||||
ours: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_sell(&mut self, trade: PendingTrade) -> Result<(), String> {
|
|
||||||
let (my_offer, their_offer) = {
|
|
||||||
let my_offer_index = trade
|
|
||||||
.which_party(self.client.uid().ok_or("Failed to get uid")?)
|
|
||||||
.ok_or("Failed to get offer index")?;
|
|
||||||
let their_offer_index = if my_offer_index == 0 { 1 } else { 0 };
|
|
||||||
|
|
||||||
(
|
|
||||||
&trade.offers[my_offer_index],
|
|
||||||
&trade.offers[their_offer_index],
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let inventories = self.client.inventories();
|
|
||||||
let my_inventory = inventories.get(self.client.entity()).unwrap();
|
|
||||||
let them = self
|
|
||||||
.client
|
|
||||||
.state()
|
|
||||||
.ecs()
|
|
||||||
.entity_from_uid(trade.parties[1])
|
|
||||||
.ok_or("Failed to find player".to_string())?;
|
|
||||||
let their_inventory = inventories
|
let their_inventory = inventories
|
||||||
.get(them)
|
.get(them)
|
||||||
.ok_or("Failed to find inventory".to_string())?;
|
.ok_or("Failed to find inventory".to_string())?;
|
||||||
let their_coins = their_inventory
|
let their_coins = their_inventory
|
||||||
.get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string()))
|
.get_slot_of_item_by_def_id(&ItemDefinitionIdOwned::Simple(COINS.to_string()))
|
||||||
.ok_or("Failed to find coins")?;
|
.ok_or("Failed to find coins")?;
|
||||||
let my_offered_items_value = my_offer.into_iter().fold(0, |acc, (slot_id, quantity)| {
|
let their_total_coin_amount = their_inventory
|
||||||
if let Some(item) = my_inventory.get(slot_id.clone()) {
|
.get(their_coins)
|
||||||
let item_value = self
|
.map(|item| item.amount() as i32)
|
||||||
.sell_prices
|
.unwrap_or(0);
|
||||||
.get(&item.persistence_item_id())
|
let (mut their_offered_coin_amount, mut my_offered_coin_amount) = (0, 0);
|
||||||
.unwrap_or(&0);
|
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.clone()) {
|
||||||
|
let item_id = item.persistence_item_id();
|
||||||
|
|
||||||
acc + (item_value * quantity)
|
let item_value = if item_id == COINS {
|
||||||
} else {
|
their_offered_coin_amount = *quantity as i32;
|
||||||
acc
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let their_offered_coins = their_offer
|
|
||||||
.into_iter()
|
|
||||||
.find_map(|(slot_id, quantity)| {
|
|
||||||
let item = if let Some(item) = their_inventory.get(slot_id.clone()) {
|
|
||||||
item
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
if item.item_definition_id()
|
1
|
||||||
== ItemDefinitionId::Simple("common.items.utility.coins".into())
|
} else {
|
||||||
{
|
self.buy_prices
|
||||||
Some(quantity)
|
.get(&item_id)
|
||||||
} else {
|
.map(|int| *int as i32)
|
||||||
None
|
.unwrap_or_else(|| {
|
||||||
}
|
self.sell_prices
|
||||||
})
|
.get(&item_id)
|
||||||
.unwrap_or(&0);
|
.map(|int| 0 - *int as i32)
|
||||||
let difference: i32 = my_offered_items_value as i32 - *their_offered_coins as i32;
|
.unwrap_or(0)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
let mut their_items_to_remove = Vec::new();
|
acc.saturating_add(item_value.saturating_mul(*quantity as i32))
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let my_offered_items_value =
|
||||||
|
my_offer
|
||||||
|
.into_iter()
|
||||||
|
.fold(0, |acc: i32, (slot_id, quantity)| {
|
||||||
|
if let Some(item) = my_inventory.get(slot_id.clone()) {
|
||||||
|
let item_id = item.persistence_item_id();
|
||||||
|
|
||||||
for (item_id, quantity) in their_offer {
|
let item_value = if item_id == COINS {
|
||||||
let item = their_inventory
|
my_offered_coin_amount = *quantity as i32;
|
||||||
.get(item_id.clone())
|
|
||||||
.ok_or("Failed to find item".to_string())?;
|
|
||||||
|
|
||||||
if item.item_definition_id()
|
1
|
||||||
!= ItemDefinitionId::Simple("common.items.utility.coins".into())
|
} else {
|
||||||
{
|
self.sell_prices
|
||||||
their_items_to_remove.push((item_id.clone(), *quantity));
|
.get(&item_id)
|
||||||
}
|
.map(|int| *int as i32)
|
||||||
}
|
.unwrap_or_else(|| {
|
||||||
|
self.buy_prices
|
||||||
|
.get(&item_id)
|
||||||
|
.map(|int| 0 - *int as i32)
|
||||||
|
.unwrap_or(i32::MAX)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
acc.saturating_add(item_value.saturating_mul(*quantity as i32))
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mut my_items_to_remove = Vec::new();
|
let mut my_items_to_remove = Vec::new();
|
||||||
|
|
||||||
for (item_id, quantity) in my_offer {
|
for (slot_id, amount) in my_offer {
|
||||||
let item = my_inventory
|
let item = my_inventory
|
||||||
.get(item_id.clone())
|
.get(slot_id.clone())
|
||||||
.ok_or("Failed to find item".to_string())?;
|
.ok_or("Failed to get item")?;
|
||||||
|
let item_id = item.persistence_item_id();
|
||||||
|
|
||||||
if !self.sell_prices.contains_key(&item.persistence_item_id()) {
|
if item_id == COINS {
|
||||||
my_items_to_remove.push((item_id.clone(), *quantity));
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.sell_prices.contains_key(&item_id) {
|
||||||
|
my_items_to_remove.push((slot_id.clone(), *amount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut their_items_to_remove = Vec::new();
|
||||||
|
|
||||||
|
for (slot_id, amount) in their_offer {
|
||||||
|
let item = their_inventory
|
||||||
|
.get(slot_id.clone())
|
||||||
|
.ok_or("Failed to get item")?;
|
||||||
|
|
||||||
|
let item_id = item.persistence_item_id();
|
||||||
|
|
||||||
|
if item_id == COINS {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.buy_prices.contains_key(&item_id) {
|
||||||
|
their_items_to_remove.push((slot_id.clone(), *amount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(inventories);
|
drop(inventories);
|
||||||
|
|
||||||
for (item, quantity) in their_items_to_remove {
|
if !self.is_player_notified {
|
||||||
self.client.perform_trade_action(TradeAction::RemoveItem {
|
self.send_price_info(&trade.parties[their_offer_index])?;
|
||||||
item,
|
|
||||||
quantity,
|
self.is_player_notified = true;
|
||||||
ours: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (item, quantity) in my_items_to_remove {
|
if their_offered_items_value == 0 && my_offered_items_value == 0 {
|
||||||
self.client.perform_trade_action(TradeAction::RemoveItem {
|
return Ok(());
|
||||||
item,
|
|
||||||
quantity,
|
|
||||||
ours: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !my_items_to_remove.is_empty() {
|
||||||
|
for (item, quantity) in my_items_to_remove {
|
||||||
|
self.client.perform_trade_action(TradeAction::RemoveItem {
|
||||||
|
item,
|
||||||
|
quantity,
|
||||||
|
ours: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !their_items_to_remove.is_empty() {
|
||||||
|
for (item, quantity) in their_items_to_remove {
|
||||||
|
self.client.perform_trade_action(TradeAction::RemoveItem {
|
||||||
|
item,
|
||||||
|
quantity,
|
||||||
|
ours: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if my_offered_items_value > their_total_coin_amount {
|
||||||
|
self.client.send_command(
|
||||||
|
"tell".to_string(),
|
||||||
|
vec![
|
||||||
|
self.find_name(&trade.parties[their_offer_index])
|
||||||
|
.ok_or("Failed to get uid")?
|
||||||
|
.to_string(),
|
||||||
|
format!("I need {my_offered_items_value} coins or trade value from you."),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let difference: i32 = their_offered_items_value as i32 - my_offered_items_value as i32;
|
||||||
|
|
||||||
|
// If the trade is balanced
|
||||||
if difference == 0 {
|
if difference == 0 {
|
||||||
|
// Accept
|
||||||
self.client
|
self.client
|
||||||
.perform_trade_action(TradeAction::Accept(trade.phase));
|
.perform_trade_action(TradeAction::Accept(trade.phase));
|
||||||
|
// If they are offering more
|
||||||
} else if difference.is_positive() {
|
} else if difference.is_positive() {
|
||||||
self.client.perform_trade_action(TradeAction::AddItem {
|
// If they are offering coins
|
||||||
item: their_coins,
|
if their_offered_coin_amount > 0 {
|
||||||
quantity: difference as u32,
|
// Remove their coins to balance
|
||||||
ours: false,
|
self.client.perform_trade_action(TradeAction::RemoveItem {
|
||||||
});
|
item: their_coins,
|
||||||
|
quantity: difference as u32,
|
||||||
|
ours: false,
|
||||||
|
});
|
||||||
|
// If they are not offering coins
|
||||||
|
} else {
|
||||||
|
// Add my coins to balanace
|
||||||
|
self.client.perform_trade_action(TradeAction::AddItem {
|
||||||
|
item: my_coins,
|
||||||
|
quantity: difference as u32,
|
||||||
|
ours: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// If I am offering more
|
||||||
} else if difference.is_negative() {
|
} else if difference.is_negative() {
|
||||||
self.client.perform_trade_action(TradeAction::RemoveItem {
|
// If I am offering coins
|
||||||
item: their_coins,
|
if my_offered_coin_amount > 0 {
|
||||||
quantity: difference.abs() as u32,
|
// Remove my coins to balance
|
||||||
ours: false,
|
self.client.perform_trade_action(TradeAction::RemoveItem {
|
||||||
});
|
item: my_coins,
|
||||||
|
quantity: difference.abs() as u32,
|
||||||
|
ours: true,
|
||||||
|
});
|
||||||
|
// If I am not offering coins
|
||||||
|
} else {
|
||||||
|
// Add their coins to balance
|
||||||
|
self.client.perform_trade_action(TradeAction::AddItem {
|
||||||
|
item: their_coins,
|
||||||
|
quantity: difference.abs() as u32,
|
||||||
|
ours: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -484,6 +426,27 @@ impl Bot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn send_price_info(&mut self, target: &Uid) -> Result<(), String> {
|
||||||
|
let player_name = self
|
||||||
|
.find_name(target)
|
||||||
|
.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)],
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn find_name<'a>(&'a self, uid: &Uid) -> Option<&'a String> {
|
fn find_name<'a>(&'a self, uid: &Uid) -> Option<&'a String> {
|
||||||
self.client.player_list().iter().find_map(|(id, info)| {
|
self.client.player_list().iter().find_map(|(id, info)| {
|
||||||
if id == uid {
|
if id == uid {
|
||||||
@ -495,7 +458,7 @@ impl Bot {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_uid<'a>(&'a self, name: &str) -> Option<&'a Uid> {
|
fn _find_uid<'a>(&'a self, name: &str) -> Option<&'a Uid> {
|
||||||
self.client.player_list().iter().find_map(|(id, info)| {
|
self.client.player_list().iter().find_map(|(id, info)| {
|
||||||
if info.player_alias == name {
|
if info.player_alias == name {
|
||||||
Some(id)
|
Some(id)
|
||||||
@ -505,7 +468,7 @@ impl Bot {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_uuid(&self, name: &str) -> Option<String> {
|
fn _find_uuid(&self, name: &str) -> Option<String> {
|
||||||
self.client.player_list().iter().find_map(|(_, info)| {
|
self.client.player_list().iter().find_map(|(_, info)| {
|
||||||
if info.player_alias == name {
|
if info.player_alias == name {
|
||||||
Some(info.uuid.to_string())
|
Some(info.uuid.to_string())
|
||||||
|
@ -4,9 +4,6 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
env::var,
|
env::var,
|
||||||
fs::{read_to_string, write},
|
fs::{read_to_string, write},
|
||||||
sync::{Arc, Mutex},
|
|
||||||
thread::{sleep, spawn},
|
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use bot::Bot;
|
use bot::Bot;
|
||||||
@ -29,7 +26,7 @@ impl Config {
|
|||||||
toml::from_str::<Config>(&config_file_content).map_err(|error| error.to_string())
|
toml::from_str::<Config>(&config_file_content).map_err(|error| error.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&self) -> Result<(), String> {
|
fn _write(&self) -> Result<(), String> {
|
||||||
let config_path = var("CONFIG_PATH").map_err(|error| error.to_string())?;
|
let config_path = var("CONFIG_PATH").map_err(|error| error.to_string())?;
|
||||||
let config_string = toml::to_string(self).map_err(|error| error.to_string())?;
|
let config_string = toml::to_string(self).map_err(|error| error.to_string())?;
|
||||||
|
|
||||||
@ -38,6 +35,8 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
let config = Config::read().unwrap();
|
let config = Config::read().unwrap();
|
||||||
let mut bot = Bot::new(
|
let mut bot = Bot::new(
|
||||||
config.username,
|
config.username,
|
||||||
@ -51,6 +50,6 @@ fn main() {
|
|||||||
bot.select_character().expect("Failed to select character");
|
bot.select_character().expect("Failed to select character");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
bot.tick().inspect_err(|error| eprintln!("{error}"));
|
let _ = bot.tick().inspect_err(|error| eprintln!("{error}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user