mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
move whole webserver to server-cli
This commit is contained in:
parent
38f4b8b644
commit
adeab73876
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -7217,7 +7217,6 @@ version = "0.15.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"atomicwrites",
|
"atomicwrites",
|
||||||
"authc",
|
"authc",
|
||||||
"axum",
|
|
||||||
"bincode",
|
"bincode",
|
||||||
"censor",
|
"censor",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -7228,7 +7227,6 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"hashbrown 0.13.2",
|
"hashbrown 0.13.2",
|
||||||
"humantime",
|
"humantime",
|
||||||
"hyper",
|
|
||||||
"itertools",
|
"itertools",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"noise",
|
"noise",
|
||||||
@ -7283,12 +7281,16 @@ dependencies = [
|
|||||||
name = "veloren-server-cli"
|
name = "veloren-server-cli"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"axum",
|
||||||
"cansi",
|
"cansi",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm 0.26.1",
|
"crossterm 0.26.1",
|
||||||
|
"hyper",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
|
"prometheus",
|
||||||
"ron 0.8.1",
|
"ron 0.8.1",
|
||||||
"serde",
|
"serde",
|
||||||
"shell-words",
|
"shell-words",
|
||||||
|
@ -47,6 +47,12 @@ tracing = { workspace = true }
|
|||||||
ron = { workspace = true }
|
ron = { workspace = true }
|
||||||
serde = { workspace = true, features = [ "rc", "derive" ]}
|
serde = { workspace = true, features = [ "rc", "derive" ]}
|
||||||
|
|
||||||
|
#HTTP
|
||||||
|
axum = { version = "0.6.20" }
|
||||||
|
hyper = "0.14.26"
|
||||||
|
prometheus = { workspace = true }
|
||||||
|
chrono = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
mimalloc = "0.1.29"
|
mimalloc = "0.1.29"
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ mod settings;
|
|||||||
mod shutdown_coordinator;
|
mod shutdown_coordinator;
|
||||||
mod tui_runner;
|
mod tui_runner;
|
||||||
mod tuilog;
|
mod tuilog;
|
||||||
|
mod web;
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{Admin, ArgvApp, ArgvCommand, Message, SharedCommand, Shutdown},
|
cli::{Admin, ArgvApp, ArgvCommand, Message, SharedCommand, Shutdown},
|
||||||
shutdown_coordinator::ShutdownCoordinator,
|
shutdown_coordinator::ShutdownCoordinator,
|
||||||
@ -31,6 +32,7 @@ use std::{
|
|||||||
sync::{atomic::AtomicBool, mpsc, Arc},
|
sync::{atomic::AtomicBool, mpsc, Arc},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
use tokio::sync::Notify;
|
||||||
use tracing::{info, trace};
|
use tracing::{info, trace};
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
@ -185,7 +187,7 @@ fn main() -> io::Result<()> {
|
|||||||
info!("Starting server...");
|
info!("Starting server...");
|
||||||
|
|
||||||
let protocols_and_addresses = server_settings.gameserver_protocols.clone();
|
let protocols_and_addresses = server_settings.gameserver_protocols.clone();
|
||||||
let metrics_port = &server_settings.metrics_address.port();
|
let web_port = &settings.web_address.port();
|
||||||
// Create server
|
// Create server
|
||||||
let mut server = Server::new(
|
let mut server = Server::new(
|
||||||
server_settings,
|
server_settings,
|
||||||
@ -193,10 +195,27 @@ fn main() -> io::Result<()> {
|
|||||||
database_settings,
|
database_settings,
|
||||||
&server_data_dir,
|
&server_data_dir,
|
||||||
&|_| {},
|
&|_| {},
|
||||||
runtime,
|
Arc::clone(&runtime),
|
||||||
)
|
)
|
||||||
.expect("Failed to create server instance!");
|
.expect("Failed to create server instance!");
|
||||||
|
|
||||||
|
let registry = Arc::clone(server.metrics_registry());
|
||||||
|
let chat = server.chat_cache().clone();
|
||||||
|
let metrics_shutdown = Arc::new(Notify::new());
|
||||||
|
let metrics_shutdown_clone = Arc::clone(&metrics_shutdown);
|
||||||
|
let web_chat_secret = settings.web_chat_secret.clone();
|
||||||
|
|
||||||
|
runtime.spawn(async move {
|
||||||
|
web::run(
|
||||||
|
registry,
|
||||||
|
chat,
|
||||||
|
web_chat_secret,
|
||||||
|
settings.web_address,
|
||||||
|
metrics_shutdown_clone.notified(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
|
||||||
// Collect addresses that the server is listening to log.
|
// Collect addresses that the server is listening to log.
|
||||||
let gameserver_addresses = protocols_and_addresses
|
let gameserver_addresses = protocols_and_addresses
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -210,7 +229,7 @@ fn main() -> io::Result<()> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
?metrics_port,
|
?web_port,
|
||||||
?gameserver_addresses,
|
?gameserver_addresses,
|
||||||
"Server is ready to accept connections."
|
"Server is ready to accept connections."
|
||||||
);
|
);
|
||||||
@ -243,6 +262,7 @@ fn main() -> io::Result<()> {
|
|||||||
tick_no += 1;
|
tick_no += 1;
|
||||||
// Terminate the server if instructed to do so by the shutdown coordinator
|
// Terminate the server if instructed to do so by the shutdown coordinator
|
||||||
if shutdown_coordinator.check(&mut server, &settings) {
|
if shutdown_coordinator.check(&mut server, &settings) {
|
||||||
|
metrics_shutdown.notify_one();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{fs, path::PathBuf};
|
use std::{
|
||||||
|
fs,
|
||||||
|
net::{Ipv4Addr, SocketAddr},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@ -7,6 +11,10 @@ use tracing::warn;
|
|||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub update_shutdown_grace_period_secs: u32,
|
pub update_shutdown_grace_period_secs: u32,
|
||||||
pub update_shutdown_message: String,
|
pub update_shutdown_message: String,
|
||||||
|
pub web_address: SocketAddr,
|
||||||
|
/// SECRET API HEADER used to access the chat api, if disabled the API is
|
||||||
|
/// unreachable
|
||||||
|
pub web_chat_secret: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
@ -14,6 +22,8 @@ impl Default for Settings {
|
|||||||
Self {
|
Self {
|
||||||
update_shutdown_grace_period_secs: 120,
|
update_shutdown_grace_period_secs: 120,
|
||||||
update_shutdown_message: "The server is restarting for an update".to_owned(),
|
update_shutdown_message: "The server is restarting for an update".to_owned(),
|
||||||
|
web_address: SocketAddr::from((Ipv4Addr::LOCALHOST, 14005)),
|
||||||
|
web_chat_secret: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,63 +5,16 @@ use axum::{
|
|||||||
routing::get,
|
routing::get,
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::DateTime;
|
||||||
use common::comp::ChatMsg;
|
|
||||||
use hyper::{Request, StatusCode};
|
use hyper::{Request, StatusCode};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer};
|
||||||
use std::{
|
use server::chat::ChatCache;
|
||||||
borrow::Cow, collections::VecDeque, mem::size_of, ops::Sub, str::FromStr, sync::Arc,
|
use std::{borrow::Cow, str::FromStr};
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
/// The chat cache gets it data from the gameserver and will keep it for some
|
|
||||||
/// time It will be made available for its consumers, the REST Api
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ChatCache {
|
|
||||||
messages: Arc<Mutex<VecDeque<ChatMessage>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Keep Size small, so we dont have to Clone much for each request.
|
/// Keep Size small, so we dont have to Clone much for each request.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ChatToken {
|
struct ChatToken {
|
||||||
secret_token: Cow<'static, str>,
|
secret_token: Option<Cow<'static, str>>,
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ChatExporter {
|
|
||||||
messages: Arc<Mutex<VecDeque<ChatMessage>>>,
|
|
||||||
keep_duration: chrono::Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatExporter {
|
|
||||||
pub fn send(&self, msg: ChatMsg) {
|
|
||||||
let time = Utc::now();
|
|
||||||
let drop_older_than = time.sub(self.keep_duration);
|
|
||||||
let mut messages = self.messages.blocking_lock();
|
|
||||||
while let Some(msg) = messages.front() && msg.time < drop_older_than {
|
|
||||||
messages.pop_front();
|
|
||||||
}
|
|
||||||
messages.push_back(ChatMessage { time, msg });
|
|
||||||
const MAX_CACHE_BYTES: usize = 10_000_000; // approx. because HashMap allocates on Heap
|
|
||||||
if messages.len() * size_of::<ChatMessage>() > MAX_CACHE_BYTES {
|
|
||||||
let msg_count = messages.len();
|
|
||||||
tracing::debug!(?msg_count, "shrinking cache");
|
|
||||||
messages.shrink_to_fit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChatCache {
|
|
||||||
pub fn new(keep_duration: Duration) -> (Self, ChatExporter) {
|
|
||||||
let messages: Arc<Mutex<VecDeque<ChatMessage>>> = Default::default();
|
|
||||||
let messages_clone = Arc::clone(&messages);
|
|
||||||
let keep_duration = chrono::Duration::from_std(keep_duration).unwrap();
|
|
||||||
|
|
||||||
(Self { messages }, ChatExporter {
|
|
||||||
messages: messages_clone,
|
|
||||||
keep_duration,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn validate_secret<B>(
|
async fn validate_secret<B>(
|
||||||
@ -69,22 +22,25 @@ async fn validate_secret<B>(
|
|||||||
req: Request<B>,
|
req: Request<B>,
|
||||||
next: Next<B>,
|
next: Next<B>,
|
||||||
) -> Result<Response, StatusCode> {
|
) -> Result<Response, StatusCode> {
|
||||||
|
// check if this endpoint is disabled
|
||||||
|
let secret_token = token.secret_token.ok_or(StatusCode::METHOD_NOT_ALLOWED)?;
|
||||||
|
|
||||||
pub const X_SECRET_TOKEN: &str = "X-Secret-Token";
|
pub const X_SECRET_TOKEN: &str = "X-Secret-Token";
|
||||||
let session_cookie = req
|
let session_cookie = req
|
||||||
.headers()
|
.headers()
|
||||||
.get(X_SECRET_TOKEN)
|
.get(X_SECRET_TOKEN)
|
||||||
.ok_or(StatusCode::UNAUTHORIZED)?;
|
.ok_or(StatusCode::UNAUTHORIZED)?;
|
||||||
|
|
||||||
if session_cookie.as_bytes() != token.secret_token.as_bytes() {
|
if session_cookie.as_bytes() != secret_token.as_bytes() {
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
return Err(StatusCode::UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(next.run(req).await)
|
Ok(next.run(req).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn router(cache: ChatCache, secret_token: String) -> Router {
|
pub fn router(cache: ChatCache, secret_token: Option<String>) -> Router {
|
||||||
let token = ChatToken {
|
let token = ChatToken {
|
||||||
secret_token: Cow::Owned(secret_token),
|
secret_token: secret_token.map(Cow::Owned),
|
||||||
};
|
};
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/history", get(history))
|
.route("/history", get(history))
|
||||||
@ -92,12 +48,6 @@ pub fn router(cache: ChatCache, secret_token: String) -> Router {
|
|||||||
.with_state(cache)
|
.with_state(cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
struct ChatMessage {
|
|
||||||
time: DateTime<Utc>,
|
|
||||||
msg: ChatMsg,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Params {
|
struct Params {
|
||||||
#[serde(default, deserialize_with = "empty_string_as_none")]
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
@ -2,15 +2,15 @@ use axum::{extract::State, response::IntoResponse, routing::get, Router};
|
|||||||
use core::{future::Future, ops::Deref};
|
use core::{future::Future, ops::Deref};
|
||||||
use hyper::{header, http, Body, StatusCode};
|
use hyper::{header, http, Body, StatusCode};
|
||||||
use prometheus::{Registry, TextEncoder};
|
use prometheus::{Registry, TextEncoder};
|
||||||
|
use server::chat::ChatCache;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
mod chat;
|
mod chat;
|
||||||
pub use chat::{ChatCache, ChatExporter};
|
|
||||||
|
|
||||||
pub async fn run<S, F, R>(
|
pub async fn run<S, F, R>(
|
||||||
registry: R,
|
registry: R,
|
||||||
cache: ChatCache,
|
cache: ChatCache,
|
||||||
chat_token: String,
|
chat_secret: Option<String>,
|
||||||
addr: S,
|
addr: S,
|
||||||
shutdown: F,
|
shutdown: F,
|
||||||
) -> Result<(), hyper::Error>
|
) -> Result<(), hyper::Error>
|
||||||
@ -24,7 +24,7 @@ where
|
|||||||
.with_state(registry.deref().clone());
|
.with_state(registry.deref().clone());
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("/chat/v1", chat::router(cache, chat_token))
|
.nest("/chat/v1", chat::router(cache, chat_secret))
|
||||||
.nest("/metrics", metrics)
|
.nest("/metrics", metrics)
|
||||||
.route("/health", get(|| async {}));
|
.route("/health", get(|| async {}));
|
||||||
|
|
@ -64,10 +64,6 @@ enum-map = { workspace = true }
|
|||||||
noise = { version = "0.7", default-features = false }
|
noise = { version = "0.7", default-features = false }
|
||||||
censor = "0.3"
|
censor = "0.3"
|
||||||
|
|
||||||
#HTTP
|
|
||||||
axum = { version = "0.6.20" }
|
|
||||||
hyper = "0.14.26"
|
|
||||||
|
|
||||||
rusqlite = { version = "0.28.0", features = ["array", "vtab", "bundled", "trace"] }
|
rusqlite = { version = "0.28.0", features = ["array", "vtab", "bundled", "trace"] }
|
||||||
refinery = { version = "0.8.8", features = ["rusqlite"] }
|
refinery = { version = "0.8.8", features = ["rusqlite"] }
|
||||||
|
|
||||||
|
228
server/src/chat.rs
Normal file
228
server/src/chat.rs
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use common::{
|
||||||
|
comp,
|
||||||
|
comp::{chat::KillType, ChatType, Content, Group, Player, UnresolvedChatMsg},
|
||||||
|
uid::IdMaps,
|
||||||
|
uuid::Uuid,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specs::{Join, World, WorldExt};
|
||||||
|
use std::{collections::VecDeque, ops::Sub, sync::Arc, time::Duration};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PlayerInfo {
|
||||||
|
uuid: Uuid,
|
||||||
|
alias: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub enum KillSource {
|
||||||
|
Player(PlayerInfo, KillType),
|
||||||
|
NonPlayer(String, KillType),
|
||||||
|
NonExistent(KillType),
|
||||||
|
Environment(String),
|
||||||
|
FallDamage,
|
||||||
|
Suicide,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
/// partially mapped to common::comp::ChatMsg
|
||||||
|
pub enum ChatParties {
|
||||||
|
Online(PlayerInfo),
|
||||||
|
Offline(PlayerInfo),
|
||||||
|
CommandInfo(PlayerInfo),
|
||||||
|
CommandError(PlayerInfo),
|
||||||
|
Kill(KillSource, PlayerInfo),
|
||||||
|
GroupMeta(Vec<PlayerInfo>),
|
||||||
|
Group(PlayerInfo, Vec<PlayerInfo>),
|
||||||
|
Tell(PlayerInfo, PlayerInfo),
|
||||||
|
Say(PlayerInfo),
|
||||||
|
FactionMeta(String),
|
||||||
|
Faction(PlayerInfo, String),
|
||||||
|
Region(PlayerInfo),
|
||||||
|
World(PlayerInfo),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ChatMessage {
|
||||||
|
pub time: DateTime<Utc>,
|
||||||
|
pub parties: ChatParties,
|
||||||
|
pub content: Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessagesStore = Arc<Mutex<VecDeque<ChatMessage>>>;
|
||||||
|
|
||||||
|
/// The chat cache gets it data from the gameserver and will keep it for some
|
||||||
|
/// time It will be made available for its consumers, the REST Api
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ChatCache {
|
||||||
|
pub messages: MessagesStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChatExporter {
|
||||||
|
messages: MessagesStore,
|
||||||
|
keep_duration: chrono::Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChatMessage {
|
||||||
|
fn new(chatmsg: &UnresolvedChatMsg, parties: ChatParties) -> Self {
|
||||||
|
ChatMessage {
|
||||||
|
time: Utc::now(),
|
||||||
|
content: chatmsg.content().clone(),
|
||||||
|
parties,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChatExporter {
|
||||||
|
pub fn generate(chatmsg: &UnresolvedChatMsg, ecs: &World) -> Option<ChatMessage> {
|
||||||
|
let id_maps = ecs.read_resource::<IdMaps>();
|
||||||
|
let players = ecs.read_storage::<Player>();
|
||||||
|
let player_info_from_uid = |uid| {
|
||||||
|
id_maps
|
||||||
|
.uid_entity(uid)
|
||||||
|
.and_then(|entry| players.get(entry))
|
||||||
|
.map(|player| PlayerInfo {
|
||||||
|
alias: player.alias.clone(),
|
||||||
|
uuid: player.uuid(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let group_members_from_group = |g| -> Vec<_> {
|
||||||
|
let groups = ecs.read_storage::<Group>();
|
||||||
|
(&players, &groups)
|
||||||
|
.join()
|
||||||
|
.filter_map(|(player, group)| {
|
||||||
|
if g == group {
|
||||||
|
Some(PlayerInfo {
|
||||||
|
alias: player.alias.clone(),
|
||||||
|
uuid: player.uuid(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
match &chatmsg.chat_type {
|
||||||
|
ChatType::Offline(from) => {
|
||||||
|
if let Some(player_info) = player_info_from_uid(*from) {
|
||||||
|
return Some(ChatMessage::new(chatmsg, ChatParties::Offline(player_info)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChatType::Online(from) => {
|
||||||
|
if let Some(player_info) = player_info_from_uid(*from) {
|
||||||
|
return Some(ChatMessage::new(chatmsg, ChatParties::Online(player_info)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChatType::Region(from) => {
|
||||||
|
if let Some(player_info) = player_info_from_uid(*from) {
|
||||||
|
return Some(ChatMessage::new(chatmsg, ChatParties::Region(player_info)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChatType::World(from) => {
|
||||||
|
if let Some(player_info) = player_info_from_uid(*from) {
|
||||||
|
return Some(ChatMessage::new(chatmsg, ChatParties::World(player_info)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChatType::Say(from) => {
|
||||||
|
if let Some(player_info) = player_info_from_uid(*from) {
|
||||||
|
return Some(ChatMessage::new(chatmsg, ChatParties::Say(player_info)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChatType::Tell(from, to) => {
|
||||||
|
if let (Some(from_player_info), Some(to_player_info)) =
|
||||||
|
(player_info_from_uid(*from), player_info_from_uid(*to))
|
||||||
|
{
|
||||||
|
return Some(ChatMessage::new(
|
||||||
|
chatmsg,
|
||||||
|
ChatParties::Tell(from_player_info, to_player_info),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChatType::Kill(kill_source, from) => {
|
||||||
|
let kill_source = match kill_source.clone() {
|
||||||
|
comp::chat::KillSource::Player(uid, t) => {
|
||||||
|
if let Some(player_info) = player_info_from_uid(uid) {
|
||||||
|
KillSource::Player(player_info, t)
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
comp::chat::KillSource::NonPlayer(str, t) => KillSource::NonPlayer(str, t),
|
||||||
|
comp::chat::KillSource::NonExistent(t) => KillSource::NonExistent(t),
|
||||||
|
comp::chat::KillSource::Environment(str) => KillSource::Environment(str),
|
||||||
|
comp::chat::KillSource::FallDamage => KillSource::FallDamage,
|
||||||
|
comp::chat::KillSource::Suicide => KillSource::Suicide,
|
||||||
|
comp::chat::KillSource::Other => KillSource::Other,
|
||||||
|
};
|
||||||
|
if let Some(player_info) = player_info_from_uid(*from) {
|
||||||
|
return Some(ChatMessage::new(
|
||||||
|
chatmsg,
|
||||||
|
ChatParties::Kill(kill_source, player_info),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChatType::FactionMeta(s) => {
|
||||||
|
return Some(ChatMessage::new(
|
||||||
|
chatmsg,
|
||||||
|
ChatParties::FactionMeta(s.clone()),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
ChatType::Faction(from, s) => {
|
||||||
|
if let Some(player_info) = player_info_from_uid(*from) {
|
||||||
|
return Some(ChatMessage::new(
|
||||||
|
chatmsg,
|
||||||
|
ChatParties::Faction(player_info, s.clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ChatType::GroupMeta(g) => {
|
||||||
|
let members = group_members_from_group(g);
|
||||||
|
return Some(ChatMessage::new(chatmsg, ChatParties::GroupMeta(members)));
|
||||||
|
},
|
||||||
|
ChatType::Group(from, g) => {
|
||||||
|
let members = group_members_from_group(g);
|
||||||
|
if let Some(player_info) = player_info_from_uid(*from) {
|
||||||
|
return Some(ChatMessage::new(
|
||||||
|
chatmsg,
|
||||||
|
ChatParties::Group(player_info, members),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&self, msg: ChatMessage) {
|
||||||
|
let drop_older_than = msg.time.sub(self.keep_duration);
|
||||||
|
let mut messages = self.messages.blocking_lock();
|
||||||
|
while let Some(msg) = messages.front() && msg.time < drop_older_than {
|
||||||
|
messages.pop_front();
|
||||||
|
}
|
||||||
|
messages.push_back(msg);
|
||||||
|
const MAX_CACHE_MESSAGES: usize = 10_000; // in case we have a short spam of many many messages, we dont want to keep the capacity forever
|
||||||
|
if messages.capacity() > messages.len() + MAX_CACHE_MESSAGES {
|
||||||
|
let msg_count = messages.len();
|
||||||
|
tracing::debug!(?msg_count, "shrinking cache");
|
||||||
|
messages.shrink_to_fit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChatCache {
|
||||||
|
pub fn new(keep_duration: Duration) -> (Self, ChatExporter) {
|
||||||
|
let messages: Arc<Mutex<VecDeque<ChatMessage>>> = Default::default();
|
||||||
|
let messages_clone = Arc::clone(&messages);
|
||||||
|
let keep_duration = chrono::Duration::from_std(keep_duration).unwrap();
|
||||||
|
|
||||||
|
(Self { messages }, ChatExporter {
|
||||||
|
messages: messages_clone,
|
||||||
|
keep_duration,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
pub mod automod;
|
pub mod automod;
|
||||||
mod character_creator;
|
mod character_creator;
|
||||||
|
pub mod chat;
|
||||||
pub mod chunk_generator;
|
pub mod chunk_generator;
|
||||||
mod chunk_serialize;
|
mod chunk_serialize;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
@ -31,7 +32,6 @@ pub mod sys;
|
|||||||
#[cfg(feature = "persistent_world")]
|
#[cfg(feature = "persistent_world")]
|
||||||
pub mod terrain_persistence;
|
pub mod terrain_persistence;
|
||||||
#[cfg(not(feature = "worldgen"))] mod test_world;
|
#[cfg(not(feature = "worldgen"))] mod test_world;
|
||||||
mod web;
|
|
||||||
|
|
||||||
mod weather;
|
mod weather;
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
#[cfg(not(feature = "worldgen"))]
|
#[cfg(not(feature = "worldgen"))]
|
||||||
use test_world::{IndexOwned, World};
|
use test_world::{IndexOwned, World};
|
||||||
use tokio::{runtime::Runtime, sync::Notify};
|
use tokio::runtime::Runtime;
|
||||||
use tracing::{debug, error, info, trace, warn};
|
use tracing::{debug, error, info, trace, warn};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
pub use world::{civ::WorldCivStage, sim::WorldSimStage, WorldGenerateStage};
|
pub use world::{civ::WorldCivStage, sim::WorldSimStage, WorldGenerateStage};
|
||||||
@ -124,7 +124,7 @@ use {
|
|||||||
common_state::plugin::{memory_manager::EcsWorld, PluginMgr},
|
common_state::plugin::{memory_manager::EcsWorld, PluginMgr},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{persistence::character_loader::CharacterScreenResponseKind, web::ChatCache};
|
use crate::{chat::ChatCache, persistence::character_loader::CharacterScreenResponseKind};
|
||||||
use common::comp::Anchor;
|
use common::comp::Anchor;
|
||||||
#[cfg(feature = "worldgen")]
|
#[cfg(feature = "worldgen")]
|
||||||
pub use world::{
|
pub use world::{
|
||||||
@ -211,7 +211,8 @@ pub struct Server {
|
|||||||
|
|
||||||
runtime: Arc<Runtime>,
|
runtime: Arc<Runtime>,
|
||||||
|
|
||||||
metrics_shutdown: Arc<Notify>,
|
metrics_registry: Arc<Registry>,
|
||||||
|
chat_cache: ChatCache,
|
||||||
database_settings: Arc<RwLock<DatabaseSettings>>,
|
database_settings: Arc<RwLock<DatabaseSettings>>,
|
||||||
disconnect_all_clients_requested: bool,
|
disconnect_all_clients_requested: bool,
|
||||||
|
|
||||||
@ -483,21 +484,8 @@ impl Server {
|
|||||||
state.ecs_mut().insert(DeletedEntities::default());
|
state.ecs_mut().insert(DeletedEntities::default());
|
||||||
|
|
||||||
let network = Network::new_with_registry(Pid::new(), &runtime, ®istry);
|
let network = Network::new_with_registry(Pid::new(), &runtime, ®istry);
|
||||||
let metrics_shutdown = Arc::new(Notify::new());
|
|
||||||
let metrics_shutdown_clone = Arc::clone(&metrics_shutdown);
|
|
||||||
let addr = settings.metrics_address;
|
|
||||||
let (chat_cache, chat_tracker) = ChatCache::new(Duration::from_secs(60));
|
let (chat_cache, chat_tracker) = ChatCache::new(Duration::from_secs(60));
|
||||||
state.ecs_mut().insert(chat_tracker);
|
state.ecs_mut().insert(chat_tracker);
|
||||||
runtime.spawn(async move {
|
|
||||||
web::run(
|
|
||||||
Arc::clone(®istry),
|
|
||||||
chat_cache,
|
|
||||||
"secretpassword".to_string(),
|
|
||||||
addr,
|
|
||||||
metrics_shutdown_clone.notified(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut printed_quic_warning = false;
|
let mut printed_quic_warning = false;
|
||||||
for protocol in &settings.gameserver_protocols {
|
for protocol in &settings.gameserver_protocols {
|
||||||
@ -603,7 +591,8 @@ impl Server {
|
|||||||
connection_handler,
|
connection_handler,
|
||||||
runtime,
|
runtime,
|
||||||
|
|
||||||
metrics_shutdown,
|
metrics_registry: registry,
|
||||||
|
chat_cache,
|
||||||
database_settings,
|
database_settings,
|
||||||
disconnect_all_clients_requested: false,
|
disconnect_all_clients_requested: false,
|
||||||
|
|
||||||
@ -668,6 +657,12 @@ impl Server {
|
|||||||
/// Get a reference to the server's world.
|
/// Get a reference to the server's world.
|
||||||
pub fn world(&self) -> &World { &self.world }
|
pub fn world(&self) -> &World { &self.world }
|
||||||
|
|
||||||
|
/// Get a reference to the Metrics Registry
|
||||||
|
pub fn metrics_registry(&self) -> &Arc<Registry> { &self.metrics_registry }
|
||||||
|
|
||||||
|
/// Get a reference to the Chat Cache
|
||||||
|
pub fn chat_cache(&self) -> &ChatCache { &self.chat_cache }
|
||||||
|
|
||||||
fn parse_locations(&self, character_list_data: &mut [CharacterItem]) {
|
fn parse_locations(&self, character_list_data: &mut [CharacterItem]) {
|
||||||
character_list_data.iter_mut().for_each(|c| {
|
character_list_data.iter_mut().for_each(|c| {
|
||||||
let name = c
|
let name = c
|
||||||
@ -1497,8 +1492,6 @@ impl Server {
|
|||||||
|
|
||||||
impl Drop for Server {
|
impl Drop for Server {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.metrics_shutdown.notify_one();
|
|
||||||
|
|
||||||
self.state
|
self.state
|
||||||
.notify_players(ServerGeneral::Disconnect(DisconnectReason::Shutdown));
|
.notify_players(ServerGeneral::Disconnect(DisconnectReason::Shutdown));
|
||||||
|
|
||||||
|
@ -160,7 +160,6 @@ impl CalendarMode {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub gameserver_protocols: Vec<Protocol>,
|
pub gameserver_protocols: Vec<Protocol>,
|
||||||
pub metrics_address: SocketAddr,
|
|
||||||
pub auth_server_address: Option<String>,
|
pub auth_server_address: Option<String>,
|
||||||
pub max_players: u16,
|
pub max_players: u16,
|
||||||
pub world_seed: u32,
|
pub world_seed: u32,
|
||||||
@ -202,7 +201,6 @@ impl Default for Settings {
|
|||||||
address: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 14004)),
|
address: SocketAddr::from((Ipv4Addr::UNSPECIFIED, 14004)),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
metrics_address: SocketAddr::from((Ipv4Addr::LOCALHOST, 14005)),
|
|
||||||
auth_server_address: Some("https://auth.veloren.net".into()),
|
auth_server_address: Some("https://auth.veloren.net".into()),
|
||||||
world_seed: DEFAULT_WORLD_SEED,
|
world_seed: DEFAULT_WORLD_SEED,
|
||||||
server_name: "Veloren Server".into(),
|
server_name: "Veloren Server".into(),
|
||||||
@ -285,10 +283,6 @@ impl Settings {
|
|||||||
pick_unused_port().expect("Failed to find unused port!"),
|
pick_unused_port().expect("Failed to find unused port!"),
|
||||||
)),
|
)),
|
||||||
}],
|
}],
|
||||||
metrics_address: SocketAddr::from((
|
|
||||||
Ipv4Addr::LOCALHOST,
|
|
||||||
pick_unused_port().expect("Failed to find unused port!"),
|
|
||||||
)),
|
|
||||||
auth_server_address: None,
|
auth_server_address: None,
|
||||||
// If loading the default map file, make sure the seed is also default.
|
// If loading the default map file, make sure the seed is also default.
|
||||||
world_seed: if load.map_file.is_some() {
|
world_seed: if load.map_file.is_some() {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
automod::AutoMod,
|
automod::AutoMod,
|
||||||
|
chat::ChatExporter,
|
||||||
client::Client,
|
client::Client,
|
||||||
events::{self, update_map_markers},
|
events::{self, update_map_markers},
|
||||||
persistence::PersistedComponents,
|
persistence::PersistedComponents,
|
||||||
@ -8,7 +9,6 @@ use crate::{
|
|||||||
rtsim::RtSim,
|
rtsim::RtSim,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
sys::sentinel::DeletedEntities,
|
sys::sentinel::DeletedEntities,
|
||||||
web::ChatExporter,
|
|
||||||
wiring, BattleModeBuffer, SpawnPoint,
|
wiring, BattleModeBuffer, SpawnPoint,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
@ -905,6 +905,10 @@ impl StateExt for State {
|
|||||||
|
|
||||||
let group_info = msg.get_group().and_then(|g| group_manager.group_info(*g));
|
let group_info = msg.get_group().and_then(|g| group_manager.group_info(*g));
|
||||||
|
|
||||||
|
if let Some(exported_message) = ChatExporter::generate(&msg, ecs) {
|
||||||
|
chat_exporter.send(exported_message);
|
||||||
|
}
|
||||||
|
|
||||||
let resolved_msg = msg
|
let resolved_msg = msg
|
||||||
.clone()
|
.clone()
|
||||||
.map_group(|_| group_info.map_or_else(|| "???".to_string(), |i| i.name.clone()));
|
.map_group(|_| group_info.map_or_else(|| "???".to_string(), |i| i.name.clone()));
|
||||||
@ -927,7 +931,6 @@ impl StateExt for State {
|
|||||||
| comp::ChatType::CommandError
|
| comp::ChatType::CommandError
|
||||||
| comp::ChatType::Meta
|
| comp::ChatType::Meta
|
||||||
| comp::ChatType::World(_) => {
|
| comp::ChatType::World(_) => {
|
||||||
chat_exporter.send(resolved_msg.clone());
|
|
||||||
self.notify_players(ServerGeneral::ChatMsg(resolved_msg))
|
self.notify_players(ServerGeneral::ChatMsg(resolved_msg))
|
||||||
},
|
},
|
||||||
comp::ChatType::Online(u) => {
|
comp::ChatType::Online(u) => {
|
||||||
@ -938,7 +941,6 @@ impl StateExt for State {
|
|||||||
client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chat_exporter.send(resolved_msg);
|
|
||||||
},
|
},
|
||||||
comp::ChatType::Tell(from, to) => {
|
comp::ChatType::Tell(from, to) => {
|
||||||
for (client, uid) in
|
for (client, uid) in
|
||||||
@ -948,12 +950,10 @@ impl StateExt for State {
|
|||||||
client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chat_exporter.send(resolved_msg);
|
|
||||||
},
|
},
|
||||||
comp::ChatType::Kill(kill_source, uid) => {
|
comp::ChatType::Kill(kill_source, uid) => {
|
||||||
let clients = ecs.read_storage::<Client>();
|
let clients = ecs.read_storage::<Client>();
|
||||||
let clients_count = clients.count();
|
let clients_count = clients.count();
|
||||||
chat_exporter.send(resolved_msg.clone());
|
|
||||||
// Avoid chat spam, send kill message only to group or nearby players if a
|
// Avoid chat spam, send kill message only to group or nearby players if a
|
||||||
// certain amount of clients are online
|
// certain amount of clients are online
|
||||||
if clients_count
|
if clients_count
|
||||||
@ -1008,7 +1008,6 @@ impl StateExt for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chat_exporter.send(resolved_msg);
|
|
||||||
},
|
},
|
||||||
comp::ChatType::Region(uid) => {
|
comp::ChatType::Region(uid) => {
|
||||||
let entity_opt = entity_from_uid(*uid);
|
let entity_opt = entity_from_uid(*uid);
|
||||||
@ -1021,7 +1020,6 @@ impl StateExt for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chat_exporter.send(resolved_msg);
|
|
||||||
},
|
},
|
||||||
comp::ChatType::Npc(uid) => {
|
comp::ChatType::Npc(uid) => {
|
||||||
let entity_opt = entity_from_uid(*uid);
|
let entity_opt = entity_from_uid(*uid);
|
||||||
@ -1067,7 +1065,6 @@ impl StateExt for State {
|
|||||||
client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chat_exporter.send(resolved_msg);
|
|
||||||
},
|
},
|
||||||
comp::ChatType::Group(from, g) => {
|
comp::ChatType::Group(from, g) => {
|
||||||
if group_info.is_none() {
|
if group_info.is_none() {
|
||||||
@ -1089,7 +1086,6 @@ impl StateExt for State {
|
|||||||
} else {
|
} else {
|
||||||
send_to_group(g, ecs, &resolved_msg);
|
send_to_group(g, ecs, &resolved_msg);
|
||||||
}
|
}
|
||||||
chat_exporter.send(resolved_msg);
|
|
||||||
},
|
},
|
||||||
comp::ChatType::GroupMeta(g) => {
|
comp::ChatType::GroupMeta(g) => {
|
||||||
send_to_group(g, ecs, &resolved_msg);
|
send_to_group(g, ecs, &resolved_msg);
|
||||||
|
Loading…
Reference in New Issue
Block a user