Compare commits

..

No commits in common. "706d3f4f3d96c65f621e5334e824b809de1fbd30" and "31cfa10e0fd98332c9a0e143072e3fd09234466c" have entirely different histories.

4 changed files with 67 additions and 242 deletions

146
Cargo.lock generated
View File

@ -590,12 +590,6 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
[[package]]
name = "deunicode"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00"
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@ -627,17 +621,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
]
[[package]] [[package]]
name = "divrem" name = "divrem"
version = "1.0.0" version = "1.0.0"
@ -810,47 +793,6 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "fluent"
version = "0.16.0"
source = "git+https://github.com/juliancoffee/fluent-rs.git?branch=patched#929cf9512de121cce9b4cbf1cb860cd3294a1cd9"
dependencies = [
"fluent-bundle",
"unic-langid",
]
[[package]]
name = "fluent-bundle"
version = "0.15.2"
source = "git+https://github.com/juliancoffee/fluent-rs.git?branch=patched#929cf9512de121cce9b4cbf1cb860cd3294a1cd9"
dependencies = [
"fluent-langneg",
"fluent-syntax",
"intl-memoizer",
"intl_pluralrules",
"rustc-hash",
"self_cell 0.10.3",
"smallvec",
"unic-langid",
]
[[package]]
name = "fluent-langneg"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94"
dependencies = [
"unic-langid",
]
[[package]]
name = "fluent-syntax"
version = "0.11.0"
source = "git+https://github.com/juliancoffee/fluent-rs.git?branch=patched#929cf9512de121cce9b4cbf1cb860cd3294a1cd9"
dependencies = [
"thiserror",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -1313,24 +1255,6 @@ 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 = "0175f63815ce00183bf755155ad0cb48c65226c5d17a724e369c25418d2b7699" checksum = "0175f63815ce00183bf755155ad0cb48c65226c5d17a724e369c25418d2b7699"
[[package]]
name = "intl-memoizer"
version = "0.5.1"
source = "git+https://github.com/juliancoffee/fluent-rs.git?branch=patched#929cf9512de121cce9b4cbf1cb860cd3294a1cd9"
dependencies = [
"type-map",
"unic-langid",
]
[[package]]
name = "intl_pluralrules"
version = "7.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
dependencies = [
"unic-langid",
]
[[package]] [[package]]
name = "ipconfig" name = "ipconfig"
version = "0.3.2" version = "0.3.2"
@ -2301,21 +2225,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "self_cell"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d"
dependencies = [
"self_cell 1.0.4",
]
[[package]]
name = "self_cell"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a"
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.23" version = "1.0.23"
@ -2628,15 +2537,6 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -2832,7 +2732,6 @@ dependencies = [
"toml", "toml",
"vek", "vek",
"veloren-client", "veloren-client",
"veloren-client-i18n",
"veloren-common", "veloren-common",
"veloren-common-net", "veloren-common-net",
"veloren-world", "veloren-world",
@ -2869,15 +2768,6 @@ dependencies = [
"nom", "nom",
] ]
[[package]]
name = "type-map"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f"
dependencies = [
"rustc-hash",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.17.0" version = "1.17.0"
@ -2890,24 +2780,6 @@ version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea"
[[package]]
name = "unic-langid"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44"
dependencies = [
"unic-langid-impl",
]
[[package]]
name = "unic-langid-impl"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5"
dependencies = [
"tinystr",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.15" version = "0.3.15"
@ -3007,24 +2879,6 @@ dependencies = [
"veloren-network", "veloren-network",
] ]
[[package]]
name = "veloren-client-i18n"
version = "0.13.0"
source = "git+https://gitlab.com/veloren/veloren?branch=master#9452500f169316264f2f71096531e9d9b5e87e19"
dependencies = [
"deunicode",
"fluent",
"fluent-bundle",
"fluent-syntax",
"hashbrown 0.14.5",
"intl-memoizer",
"serde",
"tracing",
"unic-langid",
"veloren-common-assets",
"veloren-common-i18n",
]
[[package]] [[package]]
name = "veloren-common" name = "veloren-common"
version = "0.10.0" version = "0.10.0"

View File

@ -11,7 +11,6 @@ veloren-common = { git = "https://gitlab.com/veloren/veloren", branch = "master"
veloren-common-net = { git = "https://gitlab.com/veloren/veloren", branch = "master" } veloren-common-net = { git = "https://gitlab.com/veloren/veloren", branch = "master" }
veloren-client = { git = "https://gitlab.com/veloren/veloren", branch = "master" } 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" }
veloren-client-i18n = { 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" log = "0.4.22"

View File

@ -2,9 +2,7 @@
A bot that buys, sells and trades with players. A bot that buys, sells and trades with players.
The bot is containerized and can be run without compiling or building anything. Alternatively, you The bot is containerized and can be run without compiling or building anything. Alternatively, you can clone this repository and build the image yourself or build the binary directly with Cargo if you are familiar with Rust.
can clone this repository and build the image yourself or build the binary directly with Cargo if
you are familiar with Rust.
## Prerequisites ## Prerequisites
@ -12,8 +10,7 @@ You must have either [Docker](docker.com) or [Podman](podman.io) installed.
## Usage ## Usage
All of these steps can be done with Docker but Podman is shown for the examples. If you use All of these steps can be done with Docker but Podman is shown for the examples. If you use Docker, just replace "podman" with "docker" in your commands.
Docker, just replace "podman" with "docker" in your commands.
### Setup ### Setup
@ -33,8 +30,7 @@ Then create a secret to pass the file securely to the container.
podman secret create secrets.toml secrets.toml podman secret create secrets.toml secrets.toml
``` ```
You will also need a "config.toml" and it needs it be in a "config" directory that can be mounted You will also need a "config.toml" and it needs it be in a "config" directory that can be mounted to the container:
to the container:
```toml ```toml
# config/config.toml # config/config.toml
@ -75,18 +71,14 @@ Clone this repository. From the project root:
podman build . -t trade_bot podman build . -t trade_bot
``` ```
Then follow the [above](#running) steps with the tag "trade_bot" instead of Then follow the [above](#running) steps with the tag "trade_bot" instead of "git.jeffa.io/jeff/trade_bot".
"git.jeffa.io/jeff/trade_bot".
### In-Game Commands ### In-Game Commands
The bot is able to respond to the following commands, which must be sent via "/tell". The bot is able to respond to the following commands, which must be sent via "/tell".
- `price [search term]`: Returns the buy/sell offers of any item whose item definition ID contains - `price [search term]`: Returns the buy/sell offers of any item whose item definition ID contains the search term.
the search term. - `admin_access`: Admin-only, prompts the bot to send a trade invite to the sender, after which it will give away and accept any items until the trade ends.
- `admin_access`: Admin-only, prompts the bot to send a trade invite to the sender, after which it
will give away and accept any items until the trade ends.
- `sort [count (optional)]`: Admin-only, sorts the inventory once or the given number of times. - `sort [count (optional)]`: Admin-only, sorts the inventory once or the given number of times.
- `pos [x] [y] [z]`: Admin-only, sets the bot's desired position where it will try to stand (must - `position [x] [y] [z]`: Admin-only, sets the bot's desired position where it will try to stand (must be close to the character)
be close to the bot's current position) - `orientation [0-360]`: Admin-only, sets the bot's desired orientation (or facing direction)
- `ori [0-360]`: Admin-only, sets the bot's desired orientation (or facing direction)

View File

@ -144,8 +144,7 @@ impl Bot {
/// processing trade actions. /// processing trade actions.
/// ///
/// This function should be modified with care. In addition to being the bot's main loop, it /// This function should be modified with care. In addition to being the bot's main loop, it
/// also accepts incoming trade invites, which has a potential for error if the bot accepts an /// also accepts incoming trade invites, which has a potential for error if the bot accepts an /// invite while in the wrong trade mode.
/// invite while in the wrong trade mode.
pub fn tick(&mut self) -> Result<(), String> { pub fn tick(&mut self) -> Result<(), String> {
let veloren_events = self let veloren_events = self
.client .client
@ -222,6 +221,34 @@ impl Bot {
let command = split_content.next().unwrap_or_default(); let command = split_content.next().unwrap_or_default();
let price_correction_message = "Use the format 'price [search_term]'"; let price_correction_message = "Use the format 'price [search_term]'";
let correction_message = match command { let correction_message = match command {
"price" => {
for item_name in split_content {
self.send_price_info(&sender, &item_name.to_lowercase())?;
}
None
}
"sort" => {
if self.is_user_admin(&sender)? {
if let Some(sort_count) = split_content.next() {
let sort_count = sort_count
.parse::<u8>()
.map_err(|error| error.to_string())?;
log::info!("Sorting inventory {sort_count} times");
self.sort_count = sort_count;
} else {
self.client.sort_inventory();
log::info!("Sorting inventory once");
}
None
} else {
Some(price_correction_message)
}
}
"admin_access" => { "admin_access" => {
if self.is_user_admin(&sender)? && !self.client.is_trading() { if self.is_user_admin(&sender)? && !self.client.is_trading() {
log::info!("Providing admin access"); log::info!("Providing admin access");
@ -236,40 +263,7 @@ impl Bot {
Some(price_correction_message) Some(price_correction_message)
} }
} }
"announce" => { "position" => {
if self.is_user_admin(&sender)? {
self.handle_announcement()?;
self.last_announcement = Instant::now();
None
} else {
Some(price_correction_message)
}
}
"ori" => {
if self.is_user_admin(&sender)? {
if let Some(orientation) = split_content.next() {
self.orientation = orientation
.parse::<f32>()
.map_err(|error| error.to_string())?;
None
} else {
Some("Use the format 'orientation [0-360]'")
}
} else {
Some(price_correction_message)
}
}
"price" => {
for item_name in split_content {
self.send_price_info(&sender, &item_name)?;
}
None
}
"pos" => {
if self.is_user_admin(&sender)? { if self.is_user_admin(&sender)? {
if let (Some(x), Some(y), Some(z)) = ( if let (Some(x), Some(y), Some(z)) = (
split_content.next(), split_content.next(),
@ -292,23 +286,17 @@ impl Bot {
Some(price_correction_message) Some(price_correction_message)
} }
} }
"sort" => { "orientation" => {
if self.is_user_admin(&sender)? { if self.is_user_admin(&sender)? {
if let Some(sort_count) = split_content.next() { if let Some(orientation) = split_content.next() {
let sort_count = sort_count self.orientation = orientation
.parse::<u8>() .parse::<f32>()
.map_err(|error| error.to_string())?; .map_err(|error| error.to_string())?;
log::info!("Sorting inventory {sort_count} times");
self.sort_count = sort_count;
} else {
self.client.sort_inventory();
log::info!("Sorting inventory once");
}
None None
} else {
Some("Use the format 'orientation [0-360]'")
}
} else { } else {
Some(price_correction_message) Some(price_correction_message)
} }
@ -457,7 +445,7 @@ impl Bot {
/// Manage an active trade. /// Manage an active trade.
/// ///
/// This is a rather complex function that should be modified with care. The bot uses its buy /// This is a rather complex function that should be modified with care. The bot uses its buy
/// and sell prices to determine an item's value and determines the total value of each side of /// and sell prices to determine an item's value and determines total value of each side of
/// the trade. Coins are hard-coded to have a value of 1 each. /// the trade. Coins are hard-coded to have a value of 1 each.
/// ///
/// The bot's trading logic is as follows: /// The bot's trading logic is as follows:
@ -467,13 +455,11 @@ impl Bot {
/// 3. If their offer includes items I am not buying, remove those items unless they are coins. /// 3. If their offer includes items I am not buying, remove those items unless they are coins.
/// 4. If the trade is balanced, accept it. /// 4. If the trade is balanced, accept it.
/// 5. If the total value of their offer is greater than the total value of my offer: /// 5. If the total value of their offer is greater than the total value of my offer:
/// 1. If they are offering coins, remove them to balance. /// 1. If they are offering coins, remove some to balance.
/// 2. If they are not offering coins, add mine to balance. /// 2. If they are not offering coins, add mine to balance.
/// 6. If the total value of my offer is greater than the total value of their offer: /// 6. If the total value of my offer is greater than the total value of their offer:
/// 1. If I am offering coins, remove them to balance. /// 1. If I am offering coins, remove some to balance.
/// 2. If I am not offering coins, add theirs to balance. /// 2. If I am not offering coins, add theirs to balance.
///
/// See the inline comments for more details.
#[allow(clippy::comparison_chain)] #[allow(clippy::comparison_chain)]
fn handle_trade(&mut self, trade: PendingTrade) -> Result<(), String> { fn handle_trade(&mut self, trade: PendingTrade) -> Result<(), String> {
if trade.is_empty_trade() { if trade.is_empty_trade() {
@ -562,7 +548,7 @@ impl Bot {
} }
}); });
let mut my_item_to_remove = None; let mut my_items_to_remove = Vec::new();
for (slot_id, amount) in my_offer { for (slot_id, amount) in my_offer {
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")?;
@ -573,11 +559,11 @@ impl Bot {
} }
if !self.sell_prices.contains_key(&item_id) { if !self.sell_prices.contains_key(&item_id) {
my_item_to_remove = Some((slot_id, amount)); my_items_to_remove.push((*slot_id, *amount));
} }
} }
let mut their_item_to_remove = None; let mut their_items_to_remove = Vec::new();
for (slot_id, amount) in their_offer { for (slot_id, amount) in their_offer {
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")?;
@ -588,7 +574,7 @@ impl Bot {
} }
if !self.buy_prices.contains_key(&item_id) { if !self.buy_prices.contains_key(&item_id) {
their_item_to_remove = Some((slot_id, amount)); their_items_to_remove.push((*slot_id, *amount));
} }
} }
@ -614,38 +600,32 @@ impl Bot {
drop(inventories); drop(inventories);
// Before running any actual trade logic, remove items that are not for sale or not being if !my_items_to_remove.is_empty() {
// purchased. End this trade action if an item was removed. for (item, quantity) in my_items_to_remove {
if let Some((slot_id, quantity)) = my_item_to_remove {
self.client.perform_trade_action(TradeAction::RemoveItem { self.client.perform_trade_action(TradeAction::RemoveItem {
item: *slot_id, item,
quantity: *quantity, quantity,
ours: true, ours: true,
}); });
}
return Ok(()); return Ok(());
} }
if let Some((slot_id, quantity)) = their_item_to_remove { if !their_items_to_remove.is_empty() {
for (item, quantity) in their_items_to_remove {
self.client.perform_trade_action(TradeAction::RemoveItem { self.client.perform_trade_action(TradeAction::RemoveItem {
item: *slot_id, item,
quantity: *quantity, quantity,
ours: false, ours: false,
}); });
}
return Ok(()); return Ok(());
} }
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
// trading items according to the values set in the configuration file. In the case that
// either the bot or the other player does not have any coins, the bot will not send an
// error and the trade will remain unbalanced. In the case that we try to add more 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); self.previous_offer = Some(item_offers);