diff --git a/README.md b/README.md index 3f2f71a..7225d86 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,38 @@ The bot is containerized and can be run without compiling or building anything. can clone this repository and build the image yourself or build the binary directly with Cargo if you are familiar with Rust. +## Warnings and Notices + +- This project is **not officially supported** by the Veloren team. It will connect to the official + Veloren server by default, but the moderators have the final say in whether it is allowed or + not. **If you are asked not to use it, do not use it**. +- This is **not a cheat bot**. It does not give the player any advantage in the game. It is + intended to be a fun addition to the game that can help players trade items with each other. +- This project may have bugs. You are encouraged to report them to the author but the author takes + no responsibility for any lost items. **No such incidents have been reported so far.** +- This program **handles your password securely** and does nothing with it expect connecting to the + veloren server during launch. However, third-party video game software is often infected with + malware. You should **review the source code** or ask someone you trust to do so for you. +- You are welcome to make changes to the code or fork the project. The author is open to + contributions and suggestions. But you **must indicate that the software has changed and + distribute it under the same license**, which also requires it being open-source. You may not + distribute the modified software as if it were the original. + +## In-Game Commands + +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 name or ID contains 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. +- `announce`: Admin-only, sends the announcement message to "/world". This will reset the + announcement timer to 45 minutes. +- `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 + be close to the bot's current position) +- `ori [0-360]`: Admin-only, sets the bot's desired orientation (or facing direction) + ## Prerequisites You must have either [Docker](docker.com) or [Podman](podman.io) installed. @@ -24,7 +56,10 @@ Create a "secrets.toml" file: username = "bot_username" password = "bot_password" character = "bot_character" -admins = ["my_username"] # You may add usernames or UUIDs to this list. The only advantage of using UUIDs is that it will persist accross changes to your username or player alias. + +# You may add usernames or UUIDs to this list. The only advantage of using UUIDs is that it will +# persist accross changes to your username or player alias. +admins = ["my_username"] ``` Then create a secret to pass the file securely to the container. @@ -38,9 +73,33 @@ to the container: ```toml # config/config.toml -position = [0, 0, 0] # Change these to the desired X, Y, Z coordinates. The bot will try to stand here, but the coordinates must be close to the bot's spawn point. -orientation = 0 # 0 = North, 90 = West, 180 = South, 270 = East -announcement = "I love cheese! I am at {location}." # Optional. Announcements are sent every 45 minutes. Use {location} to insert the bot's current location. + +# Optional. The bot will connect to the official server if this is not set. +game_server = "server.veloren.net" + +# Optional. The bot will connect to the official auth server if this is not set. Be careful +# if you set this, your username and password will be sent to this server. Most servers use the +# official auth server so you can probably leave this out, even if you are using an alternate +# game server. +auth_server = "https://auth.veloren.net" + +# Optional. Change these to the desired X, Y, Z coordinates. The bot will try to stand here, but +# the coordinates must be close to the bot's spawn point. If not set, the bot will stand at its +# spawn point. Its position can be changed in-game with the "pos" command. +position = [0, 0, 0] + +# Optional. (0 = North, 90 = West, 180 = South, 270 = East) If not set, the bot will face North. +# Its orientation can be changed in-game with the "ori" command. +orientation = 0 + +# Optional. Announcements are sent every 45 minutes. Use {location} to insert the bot's current +# location. If not set, the bot will not send /world announcements but will still send /region +# announcement with usage instructions. +announcement = "I love cheese! I am at {location}." + +# The buy_prices and sell_prices tables are required. The keys are item definition IDs and the +# 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. [buy_prices] "food.cheese" = 50 @@ -77,16 +136,3 @@ podman build . -t trade_bot Then follow the [above](#running) steps with the tag "trade_bot" instead of "git.jeffa.io/jeff/trade_bot". - -### In-Game Commands - -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 - 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. -- `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 - be close to the bot's current position) -- `ori [0-360]`: Admin-only, sets the bot's desired orientation (or facing direction) diff --git a/src/bot.rs b/src/bot.rs index 1befbdd..3a6819f 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -15,7 +15,7 @@ use std::{ use hashbrown::HashMap; use tokio::runtime::Runtime; -use vek::Quaternion; +use vek::{Quaternion, Vec3}; use veloren_client::{addr::ConnectionArgs, Client, Event as VelorenEvent, SiteInfoRich, WorldExt}; use veloren_client_i18n::LocalizationHandle; use veloren_common::{ @@ -44,8 +44,8 @@ const COINS: &str = "common.items.utility.coins"; /// See the [module-level documentation](index.html) for more information. pub struct Bot { username: String, - position: [f32; 3], - orientation: f32, + position: Pos, + orientation: Ori, admins: Vec, announcement: Option, @@ -78,8 +78,8 @@ impl Bot { admins: Vec, buy_prices: HashMap, sell_prices: HashMap, - position: [f32; 3], - orientation: f32, + position: Option<[f32; 3]>, + orientation: Option, announcement: Option, ) -> Result { log::info!("Connecting to veloren"); @@ -125,6 +125,19 @@ impl Bot { clock.tick(); } + let position = if let Some(coords) = position { + Pos([coords[0], coords[1], coords[2]].into()) + } else { + client + .position() + .map(|coords| Pos(coords)) + .ok_or("Failed to get position")? + }; + let orientation = if let Some(orientation) = orientation { + Ori::new(Quaternion::rotation_z(orientation.to_radians())) + } else { + client.current::().ok_or("Failed to get orientation")? + }; let now = Instant::now(); Ok(Bot { @@ -267,11 +280,14 @@ impl Bot { } "ori" => { if self.is_user_admin(&sender)? { - if let Some(orientation) = split_content.next() { - self.orientation = orientation + if let Some(new_rotation) = split_content.next() { + let new_rotation = new_rotation .parse::() .map_err(|error| error.to_string())?; + self.orientation = + Ori::new(Quaternion::rotation_z(new_rotation.to_radians())); + None } else { Some("Use the format 'ori [0-360]'") @@ -294,13 +310,11 @@ impl Bot { split_content.next(), split_content.next(), ) { - let position = [ + self.position = Pos(Vec3::new( x.parse::().map_err(|error| error.to_string())?, y.parse::().map_err(|error| error.to_string())?, z.parse::().map_err(|error| error.to_string())?, - ]; - - self.position = position; + )); None } else { @@ -438,8 +452,8 @@ impl Bot { .sites() .into_iter() .find_map(|(_, SiteInfoRich { site, .. })| { - let x_difference = self.position[0] - site.wpos[0] as f32; - let y_difference = self.position[1] - site.wpos[1] as f32; + let x_difference = self.position.0[0] - site.wpos[0] as f32; + let y_difference = self.position.0[1] - site.wpos[1] as f32; if x_difference.abs() < 100.0 && y_difference.abs() < 100.0 { site.name.clone() @@ -824,31 +838,25 @@ impl Bot { /// Moves the character to the configured position and orientation. fn handle_position_and_orientation(&mut self) -> Result<(), String> { if let Some(current_position) = self.client.current::() { - let target_position = Pos(self.position.into()); - - if current_position != target_position { + if current_position != self.position { let entity = self.client.entity(); let ecs = self.client.state_mut().ecs(); let mut position_state = ecs.write_storage::(); position_state - .insert(entity, target_position) + .insert(entity, self.position) .map_err(|error| error.to_string())?; } } if let Some(current_orientation) = self.client.current::() { - let target_orientation = Ori::default() - .uprighted() - .rotated(Quaternion::rotation_z(self.orientation.to_radians())); - - if current_orientation != target_orientation { + if current_orientation != self.orientation { let entity = self.client.entity(); let ecs = self.client.state_mut().ecs(); let mut orientation_state = ecs.write_storage::(); orientation_state - .insert(entity, target_orientation) + .insert(entity, self.orientation) .map_err(|error| error.to_string())?; } } diff --git a/src/main.rs b/src/main.rs index f9ebac0..b7508f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,10 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] struct Config { - pub position: [f32; 3], - pub orientation: f32, + pub game_server: Option, + pub auth_server: Option, + pub position: Option<[f32; 3]>, + pub orientation: Option, pub announcement: Option, pub buy_prices: HashMap, pub sell_prices: HashMap,