mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Draft of botclient.
This commit is contained in:
parent
d8f2d55923
commit
7301182695
88
Cargo.lock
generated
88
Cargo.lock
generated
@ -1510,6 +1510,12 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endian-type"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator"
|
||||
version = "0.6.0"
|
||||
@ -1708,6 +1714,16 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "0.4.0"
|
||||
@ -3137,6 +3153,15 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nibble_vec"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.15.0"
|
||||
@ -3972,6 +3997,16 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
|
||||
|
||||
[[package]]
|
||||
name = "radix_trie"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
|
||||
dependencies = [
|
||||
"endian-type",
|
||||
"nibble_vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
@ -4333,6 +4368,29 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e1b597fcd1eeb1d6b25b493538e5aa19629eb08932184b85fef931ba87e893"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if 1.0.0",
|
||||
"dirs-next",
|
||||
"fs2",
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix 0.20.0",
|
||||
"radix_trie",
|
||||
"scopeguard",
|
||||
"smallvec",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
@ -5410,6 +5468,12 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
@ -5458,6 +5522,30 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "veloren-botclient"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"authc",
|
||||
"clap",
|
||||
"hashbrown",
|
||||
"ron",
|
||||
"rustyline",
|
||||
"serde",
|
||||
"termcolor",
|
||||
"tokio 1.3.0",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
"veloren-client",
|
||||
"veloren-common",
|
||||
"veloren-common-base",
|
||||
"veloren-common-ecs",
|
||||
"veloren-common-net",
|
||||
"veloren-common-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "veloren-client"
|
||||
version = "0.8.0"
|
||||
|
@ -8,6 +8,7 @@ members = [
|
||||
"common/net",
|
||||
"common/sys",
|
||||
"client",
|
||||
"botclient",
|
||||
"plugin/api",
|
||||
"plugin/derive",
|
||||
"plugin/rt",
|
||||
|
29
botclient/Cargo.toml
Normal file
29
botclient/Cargo.toml
Normal file
@ -0,0 +1,29 @@
|
||||
[package]
|
||||
authors = ["Avi Weinstock <aweinstock314@gmail.com>"]
|
||||
edition = "2018"
|
||||
name = "veloren-botclient"
|
||||
version = "0.8.0"
|
||||
|
||||
[dependencies]
|
||||
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "bffb5181a35c19ddfd33ee0b4aedba741aafb68d" }
|
||||
client = { package = "veloren-client", path = "../client" }
|
||||
common = { package = "veloren-common", path = "../common", features = ["no-assets"] }
|
||||
common-base = { package = "veloren-common-base", path = "../common/base" }
|
||||
common-ecs = {package = "veloren-common-ecs", path = "../common/ecs"}
|
||||
common-net = { package = "veloren-common-net", path = "../common/net" }
|
||||
common-sys = { package = "veloren-common-sys", path = "../common/sys", default-features = false }
|
||||
|
||||
hashbrown = {version = "0.9", features = ["rayon", "serde", "nightly"]}
|
||||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] }
|
||||
serde = {version = "1.0", features = [ "rc", "derive" ]}
|
||||
ron = {version = "0.6", default-features = false}
|
||||
|
||||
clap = "2.33"
|
||||
rustyline = "8.0.0"
|
||||
|
||||
# logging
|
||||
termcolor = "1.1"
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.1"
|
||||
tracing-log = "0.1.1"
|
||||
tracing-subscriber = {version = "0.2.3", default-features = false, features = ["env-filter", "fmt", "chrono", "ansi", "smallvec", "tracing-log"]}
|
241
botclient/src/main.rs
Normal file
241
botclient/src/main.rs
Normal file
@ -0,0 +1,241 @@
|
||||
#[macro_use] extern crate serde;
|
||||
|
||||
use authc::AuthClient;
|
||||
use clap::{App, AppSettings, Arg, SubCommand};
|
||||
use client::{addr::ConnectionArgs, Client};
|
||||
use common::{
|
||||
comp,
|
||||
clock::Clock,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Runtime;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
mod settings;
|
||||
|
||||
use settings::Settings;
|
||||
|
||||
pub fn init_logging() {
|
||||
use termcolor::{ColorChoice, StandardStream};
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter, FmtSubscriber};
|
||||
const RUST_LOG_ENV: &str = "RUST_LOG";
|
||||
let filter = EnvFilter::from_env(RUST_LOG_ENV).add_directive(LevelFilter::INFO.into());
|
||||
let subscriber = FmtSubscriber::builder()
|
||||
.with_max_level(Level::ERROR)
|
||||
.with_env_filter(filter);
|
||||
|
||||
subscriber
|
||||
.with_writer(|| StandardStream::stdout(ColorChoice::Auto))
|
||||
.init();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct BotCreds {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
init_logging();
|
||||
|
||||
let settings = Settings::load();
|
||||
info!("Settings: {:?}", settings);
|
||||
|
||||
let mut bc = BotClient::new(settings);
|
||||
bc.repl();
|
||||
}
|
||||
|
||||
pub struct BotClient {
|
||||
settings: Settings,
|
||||
readline: rustyline::Editor<()>,
|
||||
runtime: Arc<Runtime>,
|
||||
menu_client: Client,
|
||||
bot_clients: HashMap<String, Client>,
|
||||
clock: Clock,
|
||||
}
|
||||
|
||||
pub fn make_client(runtime: &Arc<Runtime>, server: &str) -> Client {
|
||||
let runtime2 = Arc::clone(&runtime);
|
||||
let view_distance: Option<u32> = None;
|
||||
runtime.block_on(async {
|
||||
let connection_args = ConnectionArgs::resolve(server, false)
|
||||
.await
|
||||
.expect("DNS resolution failed");
|
||||
Client::new(connection_args, view_distance, runtime2)
|
||||
.await
|
||||
.expect("Failed to connect to server")
|
||||
})
|
||||
}
|
||||
|
||||
impl BotClient {
|
||||
pub fn new(settings: Settings) -> BotClient {
|
||||
let readline = rustyline::Editor::<()>::new();
|
||||
let runtime = Arc::new(Runtime::new().unwrap());
|
||||
let menu_client: Client = make_client(&runtime, &settings.server);
|
||||
let clock = Clock::new(Duration::from_secs_f64(1.0 / 60.0));
|
||||
BotClient {
|
||||
settings,
|
||||
readline,
|
||||
runtime,
|
||||
menu_client,
|
||||
bot_clients: HashMap::new(),
|
||||
clock,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn repl(&mut self) {
|
||||
loop {
|
||||
match self.readline.readline("\n\nbotclient> ") {
|
||||
Ok(cmd) => {
|
||||
let keep_going = self.process_command(&cmd);
|
||||
self.readline.add_history_entry(cmd);
|
||||
if !keep_going {
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_command(&mut self, cmd: &str) -> bool {
|
||||
let matches = App::new("veloren-botclient")
|
||||
.version(common::util::DISPLAY_VERSION_LONG.as_str())
|
||||
.author("The veloren devs <https://gitlab.com/veloren/veloren>")
|
||||
.about("The veloren bot client allows logging in as a horde of bots for load-testing")
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.subcommand(
|
||||
SubCommand::with_name("register")
|
||||
.about("Register more bots with the auth server")
|
||||
.args(&[
|
||||
Arg::with_name("prefix").required(true),
|
||||
Arg::with_name("password").required(true),
|
||||
Arg::with_name("count"),
|
||||
]),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("login")
|
||||
.about("Login all registered bots whose username starts with a prefix")
|
||||
.args(&[Arg::with_name("prefix").required(true)]),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("tick")
|
||||
.about("Handle ticks for all logged in bots")
|
||||
)
|
||||
.get_matches_from_safe(cmd.split(" "));
|
||||
use clap::ErrorKind::*;
|
||||
match matches {
|
||||
Ok(matches) => match matches.subcommand() {
|
||||
("register", Some(matches)) => self.handle_register(
|
||||
matches.value_of("prefix").unwrap(),
|
||||
matches.value_of("password").unwrap(),
|
||||
matches
|
||||
.value_of("count")
|
||||
.and_then(|x| x.parse::<usize>().ok()),
|
||||
),
|
||||
("login", Some(matches)) => self.handle_login(matches.value_of("prefix").unwrap()),
|
||||
("tick", _) => self.handle_tick(),
|
||||
_ => {},
|
||||
},
|
||||
Err(e)
|
||||
if [HelpDisplayed, MissingRequiredArgument, UnknownArgument].contains(&e.kind) =>
|
||||
{
|
||||
println!("{}", e.message);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{:?}", e);
|
||||
return false;
|
||||
},
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn handle_register(&mut self, prefix: &str, password: &str, count: Option<usize>) {
|
||||
let usernames = match count {
|
||||
Some(n) => (0..n)
|
||||
.into_iter()
|
||||
.map(|i| format!("{}{}", prefix, i))
|
||||
.collect::<Vec<String>>(),
|
||||
None => vec![prefix.to_string()],
|
||||
};
|
||||
info!("usernames: {:?}", usernames);
|
||||
if let Some(auth_addr) = self.menu_client.server_info().auth_provider.as_ref() {
|
||||
let authc = AuthClient::new(&*auth_addr).expect("couldn't connect to auth_addr");
|
||||
for username in usernames.iter() {
|
||||
if self
|
||||
.settings
|
||||
.bot_logins
|
||||
.iter()
|
||||
.any(|x| &*x.username == &*username)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
match authc.register(username, password) {
|
||||
Ok(()) => {
|
||||
self.settings.bot_logins.push(BotCreds {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
});
|
||||
self.settings.save_to_file_warn();
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("error registering {:?}: {:?}", username, e);
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Server's auth_provider is None");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client_for_bot(&mut self, username: &str) -> &mut Client {
|
||||
let runtime = Arc::clone(&self.runtime);
|
||||
let server = self.settings.server.clone();
|
||||
self.bot_clients.entry(username.to_string()).or_insert_with(|| make_client(&runtime, &server))
|
||||
}
|
||||
|
||||
pub fn handle_login(&mut self, prefix: &str) {
|
||||
let creds: Vec<_> = self.settings.bot_logins.iter().filter(|x| x.username.starts_with(prefix)).cloned().collect();
|
||||
for cred in creds.iter() {
|
||||
let runtime = Arc::clone(&self.runtime);
|
||||
let client = self.client_for_bot(&cred.username);
|
||||
// TODO: log the clients in in parallel instead of in series
|
||||
if let Err(e) = runtime.block_on(client.register(cred.username.clone(), cred.password.clone(), |_| true)) {
|
||||
warn!("error logging in {:?}: {:?}", cred.username, e);
|
||||
}
|
||||
/*let body = comp::body::biped_large::Body {
|
||||
species: comp::body::biped_large::Species::Dullahan,
|
||||
body_type: comp::body::biped_large::BodyType::Male,
|
||||
};*/
|
||||
let body = comp::body::humanoid::Body {
|
||||
species: comp::body::humanoid::Species::Human,
|
||||
body_type: comp::body::humanoid::BodyType::Male,
|
||||
hair_style: 0,
|
||||
beard: 0,
|
||||
eyes: 0,
|
||||
accessory: 0,
|
||||
hair_color: 0,
|
||||
skin: 0,
|
||||
eye_color: 0,
|
||||
};
|
||||
client.create_character(cred.username.clone(), Some("common.items.weapons.sword.starter".to_string()), body.into());
|
||||
//client.create_character(cred.username.clone(), Some("common.items.debug.admin_stick".to_string()), body.into());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe do this automatically in a threadpool instead of as a command
|
||||
pub fn handle_tick(&mut self) {
|
||||
self.clock.tick();
|
||||
for (username, client) in self.bot_clients.iter_mut() {
|
||||
info!("cl {:?}: {:?}", username, client.character_list());
|
||||
let msgs: Result<Vec<client::Event>, client::Error> =
|
||||
client.tick(comp::ControllerInputs::default(), self.clock.dt(), |_| {});
|
||||
info!("msgs {:?}: {:?} {:?}", username, msgs, client.character_list());
|
||||
}
|
||||
}
|
||||
}
|
74
botclient/src/settings.rs
Normal file
74
botclient/src/settings.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use super::BotCreds;
|
||||
use std::{fs, path::PathBuf};
|
||||
use tracing::warn;
|
||||
|
||||
pub fn data_dir() -> PathBuf {
|
||||
let mut path = common_base::userdata_dir_workspace!();
|
||||
path.push("botclient");
|
||||
path
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Settings {
|
||||
pub server: String,
|
||||
pub bot_logins: Vec<BotCreds>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings {
|
||||
server: "localhost".to_string(),
|
||||
bot_logins: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn load() -> Self {
|
||||
let path = Self::get_settings_path();
|
||||
|
||||
if let Ok(file) = fs::File::open(&path) {
|
||||
match ron::de::from_reader(file) {
|
||||
Ok(s) => return s,
|
||||
Err(e) => {
|
||||
warn!(?e, "Failed to parse setting file! Fallback to default.");
|
||||
// Rename the corrupted settings file
|
||||
let mut new_path = path.to_owned();
|
||||
new_path.pop();
|
||||
new_path.push("settings.invalid.ron");
|
||||
if let Err(e) = std::fs::rename(&path, &new_path) {
|
||||
warn!(?e, ?path, ?new_path, "Failed to rename settings file.");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
// This is reached if either:
|
||||
// - The file can't be opened (presumably it doesn't exist)
|
||||
// - Or there was an error parsing the file
|
||||
let default_settings = Self::default();
|
||||
default_settings.save_to_file_warn();
|
||||
default_settings
|
||||
}
|
||||
|
||||
pub fn save_to_file_warn(&self) {
|
||||
if let Err(e) = self.save_to_file() {
|
||||
warn!(?e, "Failed to save settings");
|
||||
}
|
||||
}
|
||||
|
||||
fn save_to_file(&self) -> std::io::Result<()> {
|
||||
let path = Self::get_settings_path();
|
||||
if let Some(dir) = path.parent() {
|
||||
fs::create_dir_all(dir)?;
|
||||
}
|
||||
|
||||
let ron = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()).unwrap();
|
||||
fs::write(path, ron.as_bytes())
|
||||
}
|
||||
|
||||
pub fn get_settings_path() -> PathBuf {
|
||||
let mut path = data_dir();
|
||||
path.push("settings.ron");
|
||||
path
|
||||
}
|
||||
}
|
@ -68,6 +68,7 @@ use vek::*;
|
||||
|
||||
const PING_ROLLING_AVERAGE_SECS: usize = 10;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
Chat(comp::ChatMsg),
|
||||
InviteComplete {
|
||||
@ -177,7 +178,7 @@ pub struct Client {
|
||||
|
||||
/// Holds data related to the current players characters, as well as some
|
||||
/// additional state to handle UI.
|
||||
#[derive(Default)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CharacterList {
|
||||
pub characters: Vec<CharacterItem>,
|
||||
pub loading: bool,
|
||||
|
Loading…
Reference in New Issue
Block a user