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",
|
"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]]
|
[[package]]
|
||||||
name = "enum-iterator"
|
name = "enum-iterator"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@ -1708,6 +1714,16 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"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]]
|
[[package]]
|
||||||
name = "fsevent"
|
name = "fsevent"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -3137,6 +3153,15 @@ dependencies = [
|
|||||||
"winapi 0.3.9",
|
"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]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
@ -3972,6 +3997,16 @@ version = "0.6.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
|
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]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@ -4333,6 +4368,29 @@ dependencies = [
|
|||||||
"winapi 0.3.9",
|
"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]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@ -5410,6 +5468,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@ -5458,6 +5522,30 @@ dependencies = [
|
|||||||
"static_assertions",
|
"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]]
|
[[package]]
|
||||||
name = "veloren-client"
|
name = "veloren-client"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -8,6 +8,7 @@ members = [
|
|||||||
"common/net",
|
"common/net",
|
||||||
"common/sys",
|
"common/sys",
|
||||||
"client",
|
"client",
|
||||||
|
"botclient",
|
||||||
"plugin/api",
|
"plugin/api",
|
||||||
"plugin/derive",
|
"plugin/derive",
|
||||||
"plugin/rt",
|
"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;
|
const PING_ROLLING_AVERAGE_SECS: usize = 10;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Chat(comp::ChatMsg),
|
Chat(comp::ChatMsg),
|
||||||
InviteComplete {
|
InviteComplete {
|
||||||
@ -177,7 +178,7 @@ pub struct Client {
|
|||||||
|
|
||||||
/// Holds data related to the current players characters, as well as some
|
/// Holds data related to the current players characters, as well as some
|
||||||
/// additional state to handle UI.
|
/// additional state to handle UI.
|
||||||
#[derive(Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct CharacterList {
|
pub struct CharacterList {
|
||||||
pub characters: Vec<CharacterItem>,
|
pub characters: Vec<CharacterItem>,
|
||||||
pub loading: bool,
|
pub loading: bool,
|
||||||
|
Loading…
Reference in New Issue
Block a user