mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'xMAC94x/netfixA' into 'master'
xmac94x/netfixA See merge request veloren/veloren!1425
This commit is contained in:
commit
2668731a05
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -1393,9 +1393,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
|
||||
checksum = "a7a4d35f7401e948629c9c3d6638fb9bf94e0b2121e96c3b428cc4e631f3eb74"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@ -1403,9 +1403,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
|
||||
checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-cpupool"
|
||||
@ -1449,9 +1449,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc"
|
||||
checksum = "0d8764258ed64ebc5d9ed185cf86a95db5cac810269c5d20ececb32e0088abbd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
@ -4689,6 +4689,7 @@ dependencies = [
|
||||
"diesel",
|
||||
"diesel_migrations",
|
||||
"dotenv",
|
||||
"futures-channel",
|
||||
"futures-executor",
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
|
1447
client/src/lib.rs
1447
client/src/lib.rs
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
use crate::{comp::group::Group, msg::ServerMsg, sync::Uid};
|
||||
use crate::{comp::group::Group, msg::ServerGeneral, sync::Uid};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::Component;
|
||||
use specs_idvs::IdvStorage;
|
||||
@ -118,11 +118,11 @@ impl<G> ChatType<G> {
|
||||
}
|
||||
}
|
||||
impl ChatType<String> {
|
||||
pub fn server_msg<S>(self, msg: S) -> ServerMsg
|
||||
pub fn server_msg<S>(self, msg: S) -> ServerGeneral
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
ServerMsg::ChatMsg(self.chat_msg(msg))
|
||||
ServerGeneral::ChatMsg(self.chat_msg(msg))
|
||||
}
|
||||
}
|
||||
// Stores chat text, type
|
||||
|
@ -1,3 +1,4 @@
|
||||
use super::PingMsg;
|
||||
use crate::{
|
||||
character::CharacterId,
|
||||
comp,
|
||||
@ -7,13 +8,46 @@ use crate::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::*;
|
||||
|
||||
///This struct contains all messages the client might send (on different
|
||||
/// streams though). It's used to verify the correctness of the state in
|
||||
/// debug_assertions
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(clippy::clippy::large_enum_variant)]
|
||||
pub enum ClientMsg {
|
||||
///Send on the first connection ONCE to identify client intention for
|
||||
/// server
|
||||
Type(ClientType),
|
||||
///Send ONCE to register/auth to the server
|
||||
Register(ClientRegister),
|
||||
///Msg that can be send ALWAYS as soon as we are registered, e.g. `Chat`
|
||||
General(ClientGeneral),
|
||||
Ping(PingMsg),
|
||||
}
|
||||
|
||||
/*
|
||||
2nd Level Enums
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ClientType {
|
||||
/// Regular Client like Voxygen who plays the game
|
||||
Game,
|
||||
/// A Chatonly client, which doesn't want to connect via its character
|
||||
ChatOnly,
|
||||
/// A unprivileged bot, e.g. to request world information
|
||||
/// Or a privileged bot, e.g. to run admin commands used by server-cli
|
||||
Bot { privileged: bool },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ClientRegister {
|
||||
pub token_or_username: String,
|
||||
}
|
||||
|
||||
/// Messages sent from the client to the server
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ClientMsg {
|
||||
Register {
|
||||
view_distance: Option<u32>,
|
||||
token_or_username: String,
|
||||
},
|
||||
pub enum ClientGeneral {
|
||||
//Only in Character Screen
|
||||
RequestCharacterList,
|
||||
CreateCharacter {
|
||||
alias: String,
|
||||
@ -22,20 +56,15 @@ pub enum ClientMsg {
|
||||
},
|
||||
DeleteCharacter(CharacterId),
|
||||
Character(CharacterId),
|
||||
/// Request `ClientState::Registered` from an ingame state
|
||||
ExitIngame,
|
||||
/// Request `ClientState::Spectator` from a registered or ingame state
|
||||
Spectate,
|
||||
//Only in game
|
||||
ControllerInputs(comp::ControllerInputs),
|
||||
ControlEvent(comp::ControlEvent),
|
||||
ControlAction(comp::ControlAction),
|
||||
SetViewDistance(u32),
|
||||
BreakBlock(Vec3<i32>),
|
||||
PlaceBlock(Vec3<i32>, Block),
|
||||
Ping,
|
||||
Pong,
|
||||
/// Send the chat message or command to be processed by the server
|
||||
ChatMsg(String),
|
||||
ExitInGame,
|
||||
PlayerPhysics {
|
||||
pos: comp::Pos,
|
||||
vel: comp::Vel,
|
||||
@ -44,9 +73,78 @@ pub enum ClientMsg {
|
||||
TerrainChunkRequest {
|
||||
key: Vec2<i32>,
|
||||
},
|
||||
Disconnect,
|
||||
Terminate,
|
||||
UnlockSkill(Skill),
|
||||
RefundSkill(Skill),
|
||||
UnlockSkillGroup(SkillGroupType),
|
||||
//Always possible
|
||||
ChatMsg(String),
|
||||
Disconnect,
|
||||
Terminate,
|
||||
}
|
||||
|
||||
impl ClientMsg {
|
||||
pub fn verify(
|
||||
&self,
|
||||
c_type: ClientType,
|
||||
registered: bool,
|
||||
in_game: Option<super::ClientInGame>,
|
||||
) -> bool {
|
||||
match self {
|
||||
ClientMsg::Type(t) => c_type == *t,
|
||||
ClientMsg::Register(_) => !registered && in_game.is_none(),
|
||||
ClientMsg::General(g) => {
|
||||
registered
|
||||
&& match g {
|
||||
ClientGeneral::RequestCharacterList
|
||||
| ClientGeneral::CreateCharacter { .. }
|
||||
| ClientGeneral::DeleteCharacter(_) => {
|
||||
c_type != ClientType::ChatOnly && in_game.is_none()
|
||||
},
|
||||
ClientGeneral::Character(_) | ClientGeneral::Spectate => {
|
||||
c_type == ClientType::Game && in_game.is_none()
|
||||
},
|
||||
//Only in game
|
||||
ClientGeneral::ControllerInputs(_)
|
||||
| ClientGeneral::ControlEvent(_)
|
||||
| ClientGeneral::ControlAction(_)
|
||||
| ClientGeneral::SetViewDistance(_)
|
||||
| ClientGeneral::BreakBlock(_)
|
||||
| ClientGeneral::PlaceBlock(_, _)
|
||||
| ClientGeneral::ExitInGame
|
||||
| ClientGeneral::PlayerPhysics { .. }
|
||||
| ClientGeneral::TerrainChunkRequest { .. }
|
||||
| ClientGeneral::UnlockSkill(_)
|
||||
| ClientGeneral::RefundSkill(_)
|
||||
| ClientGeneral::UnlockSkillGroup(_) => {
|
||||
c_type == ClientType::Game && in_game.is_some()
|
||||
},
|
||||
//Always possible
|
||||
ClientGeneral::ChatMsg(_)
|
||||
| ClientGeneral::Disconnect
|
||||
| ClientGeneral::Terminate => true,
|
||||
}
|
||||
},
|
||||
ClientMsg::Ping(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
end of 2nd level Enums
|
||||
*/
|
||||
|
||||
impl Into<ClientMsg> for ClientType {
|
||||
fn into(self) -> ClientMsg { ClientMsg::Type(self) }
|
||||
}
|
||||
|
||||
impl Into<ClientMsg> for ClientRegister {
|
||||
fn into(self) -> ClientMsg { ClientMsg::Register(self) }
|
||||
}
|
||||
|
||||
impl Into<ClientMsg> for ClientGeneral {
|
||||
fn into(self) -> ClientMsg { ClientMsg::General(self) }
|
||||
}
|
||||
|
||||
impl Into<ClientMsg> for PingMsg {
|
||||
fn into(self) -> ClientMsg { ClientMsg::Ping(self) }
|
||||
}
|
||||
|
@ -1,27 +1,32 @@
|
||||
pub mod client;
|
||||
pub mod ecs_packet;
|
||||
pub mod server;
|
||||
pub mod world_packet;
|
||||
|
||||
// Reexports
|
||||
pub use self::{
|
||||
client::ClientMsg,
|
||||
client::{ClientGeneral, ClientMsg, ClientRegister, ClientType},
|
||||
ecs_packet::EcsCompPacket,
|
||||
server::{
|
||||
CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate,
|
||||
RegisterError, RequestStateError, ServerInfo, ServerMsg,
|
||||
RegisterError, ServerGeneral, ServerInfo, ServerInit, ServerMsg, ServerRegisterAnswer,
|
||||
},
|
||||
world_packet::WorldMapMsg,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ClientState {
|
||||
Pending,
|
||||
Connected,
|
||||
Registered,
|
||||
pub enum ClientInGame {
|
||||
Spectator,
|
||||
Character,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum PingMsg {
|
||||
Ping,
|
||||
Pong,
|
||||
}
|
||||
|
||||
pub const MAX_BYTES_CHAT_MSG: usize = 256;
|
||||
|
||||
pub enum ChatMsgValidationError {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::{ClientState, EcsCompPacket};
|
||||
use super::{ClientType, EcsCompPacket, PingMsg};
|
||||
use crate::{
|
||||
character::CharacterItem,
|
||||
comp,
|
||||
@ -14,6 +14,25 @@ use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use vek::*;
|
||||
|
||||
///This struct contains all messages the server might send (on different
|
||||
/// streams though)
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ServerMsg {
|
||||
/// Basic info about server, send ONCE, clients need it to Register
|
||||
Info(ServerInfo),
|
||||
/// Initial data package, send BEFORE Register ONCE. Not Register relevant
|
||||
Init(ServerInit),
|
||||
/// Result to `ClientMsg::Register`. send ONCE
|
||||
RegisterAnswer(ServerRegisterAnswer),
|
||||
///Msg that can be send ALWAYS as soon as client is registered, e.g. `Chat`
|
||||
General(ServerGeneral),
|
||||
Ping(PingMsg),
|
||||
}
|
||||
|
||||
/*
|
||||
2nd Level Enums
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerInfo {
|
||||
pub name: String,
|
||||
@ -23,6 +42,85 @@ pub struct ServerInfo {
|
||||
pub auth_provider: Option<String>,
|
||||
}
|
||||
|
||||
/// Reponse To ClientType
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[allow(clippy::clippy::large_enum_variant)]
|
||||
pub enum ServerInit {
|
||||
TooManyPlayers,
|
||||
GameSync {
|
||||
entity_package: sync::EntityPackage<EcsCompPacket>,
|
||||
time_of_day: state::TimeOfDay,
|
||||
max_group_size: u32,
|
||||
client_timeout: Duration,
|
||||
world_map: crate::msg::world_packet::WorldMapMsg,
|
||||
recipe_book: RecipeBook,
|
||||
},
|
||||
}
|
||||
|
||||
pub type ServerRegisterAnswer = Result<(), RegisterError>;
|
||||
|
||||
/// Messages sent from the server to the client
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ServerGeneral {
|
||||
//Character Screen related
|
||||
/// An error occurred while loading character data
|
||||
CharacterDataLoadError(String),
|
||||
/// A list of characters belonging to the a authenticated player was sent
|
||||
CharacterListUpdate(Vec<CharacterItem>),
|
||||
/// An error occurred while creating or deleting a character
|
||||
CharacterActionError(String),
|
||||
CharacterSuccess,
|
||||
//Ingame related
|
||||
GroupUpdate(comp::group::ChangeNotification<sync::Uid>),
|
||||
/// Indicate to the client that they are invited to join a group
|
||||
GroupInvite {
|
||||
inviter: sync::Uid,
|
||||
timeout: std::time::Duration,
|
||||
},
|
||||
/// Indicate to the client that their sent invite was not invalid and is
|
||||
/// currently pending
|
||||
InvitePending(sync::Uid),
|
||||
/// Note: this could potentially include all the failure cases such as
|
||||
/// inviting yourself in which case the `InvitePending` message could be
|
||||
/// removed and the client could consider their invite pending until
|
||||
/// they receive this message Indicate to the client the result of their
|
||||
/// invite
|
||||
InviteComplete {
|
||||
target: sync::Uid,
|
||||
answer: InviteAnswer,
|
||||
},
|
||||
/// Trigger cleanup for when the client goes back to the `Registered` state
|
||||
/// from an ingame state
|
||||
ExitInGameSuccess,
|
||||
InventoryUpdate(comp::Inventory, comp::InventoryUpdateEvent),
|
||||
TerrainChunkUpdate {
|
||||
key: Vec2<i32>,
|
||||
chunk: Result<Box<TerrainChunk>, ()>,
|
||||
},
|
||||
TerrainBlockUpdates(HashMap<Vec3<i32>, Block>),
|
||||
SetViewDistance(u32),
|
||||
Outcomes(Vec<Outcome>),
|
||||
Knockback(Vec3<f32>),
|
||||
// Always possible
|
||||
PlayerListUpdate(PlayerListUpdate),
|
||||
/// A message to go into the client chat box. The client is responsible for
|
||||
/// formatting the message and turning it into a speech bubble.
|
||||
ChatMsg(comp::ChatMsg),
|
||||
SetPlayerEntity(Uid),
|
||||
TimeOfDay(state::TimeOfDay),
|
||||
EntitySync(sync::EntitySyncPackage),
|
||||
CompSync(sync::CompSyncPackage<EcsCompPacket>),
|
||||
CreateEntity(sync::EntityPackage<EcsCompPacket>),
|
||||
DeleteEntity(Uid),
|
||||
Disconnect(DisconnectReason),
|
||||
/// Send a popup notification such as "Waypoint Saved"
|
||||
Notification(Notification),
|
||||
}
|
||||
|
||||
/*
|
||||
end of 2nd level Enums
|
||||
*/
|
||||
|
||||
/// Inform the client of updates to the player list.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PlayerListUpdate {
|
||||
@ -49,127 +147,6 @@ pub struct CharacterInfo {
|
||||
pub level: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// World map information. Note that currently, we always send the whole thing
|
||||
/// in one go, but the structure aims to try to provide information as locally
|
||||
/// as possible, so that in the future we can split up large maps into multiple
|
||||
/// WorldMapMsg fragments.
|
||||
///
|
||||
/// TODO: Update message format to make fragmentable, allowing us to send more
|
||||
/// information without running into bandwidth issues.
|
||||
///
|
||||
/// TODO: Add information for rivers (currently, we just prerender them on the
|
||||
/// server, but this is not a great solution for LoD. The map rendering code is
|
||||
/// already set up to be able to take advantage of the river rendering being
|
||||
/// split out, but the format is a little complicated for space reasons and it
|
||||
/// may take some tweaking to get right, so we avoid sending it for now).
|
||||
///
|
||||
/// TODO: measure explicit compression schemes that might save space, e.g.
|
||||
/// repeating the "small angles" optimization that works well on more detailed
|
||||
/// shadow maps intended for height maps.
|
||||
pub struct WorldMapMsg {
|
||||
/// Log base 2 of world map dimensions (width × height) in chunks.
|
||||
///
|
||||
/// NOTE: Invariant: chunk count fits in a u16.
|
||||
pub dimensions_lg: Vec2<u32>,
|
||||
/// Sea level (used to provide a base altitude).
|
||||
pub sea_level: f32,
|
||||
/// Max height (used to scale altitudes).
|
||||
pub max_height: f32,
|
||||
/// RGB+A; the alpha channel is currently unused, but will be used in the
|
||||
/// future. Entries are in the usual chunk order.
|
||||
pub rgba: Vec<u32>,
|
||||
/// Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for
|
||||
/// altitude. The remainder are currently unused, but we have plans to
|
||||
/// use 7 bits for water depth (using an integer f7 encoding), and we
|
||||
/// will find other uses for the remaining 12 bits.
|
||||
pub alt: Vec<u32>,
|
||||
/// Horizon mapping. This is a variant of shadow mapping that is
|
||||
/// specifically designed for height maps; it takes advantage of their
|
||||
/// regular structure (e.g. no holes) to compress all information needed
|
||||
/// to decide when to cast a sharp shadow into a single nagle, the "horizon
|
||||
/// angle." This is the smallest angle with the ground at which light can
|
||||
/// pass through any occluders to reach the chunk, in some chosen
|
||||
/// horizontal direction. This would not be sufficient for a more
|
||||
/// complicated 3D structure, but it works for height maps since:
|
||||
///
|
||||
/// 1. they have no gaps, so as soon as light can shine through it will
|
||||
/// always be able to do so, and
|
||||
/// 2. we only care about lighting from the top, and only from the east and
|
||||
/// west (since at a large scale like this we mostly just want to
|
||||
/// handle variable sunlight; moonlight would present more challenges
|
||||
/// but we currently have no plans to try to cast accurate shadows in
|
||||
/// moonlight).
|
||||
///
|
||||
/// Our chosen format is two pairs of vectors,
|
||||
/// with the first pair representing west-facing light (casting shadows on
|
||||
/// the left side) and the second representing east-facing light
|
||||
/// (casting shadows on the east side).
|
||||
///
|
||||
/// The pair of vectors consists of (with each vector in the usual chunk
|
||||
/// order):
|
||||
///
|
||||
/// * Horizon angle pointing east (1 byte, scaled so 1 unit = 255° / 360).
|
||||
/// We might consider switching to tangent if that represents the
|
||||
/// information we care about better.
|
||||
/// * Approximate (floor) height of maximal occluder. We currently use this
|
||||
/// to try to deliver some approximation of soft shadows, which isn't that
|
||||
/// big a deal on the world map but is probably needed in order to ensure
|
||||
/// smooth transitions between chunks in LoD view. Additionally, when we
|
||||
/// start using the shadow information to do local lighting on the world
|
||||
/// map, we'll want a quick way to test where we can go out of shadow at
|
||||
/// arbitrary heights (since the player and other entities cajn find
|
||||
/// themselves far from the ground at times). While this is only an
|
||||
/// approximation to a proper distance map, hopefully it will give us
|
||||
/// something that feels reasonable enough for Veloren's style.
|
||||
///
|
||||
/// NOTE: On compression.
|
||||
///
|
||||
/// Horizon mapping has a lot of advantages for height maps (simple, easy to
|
||||
/// understand, doesn't require any fancy math or approximation beyond
|
||||
/// precision loss), though it loses a few of them by having to store
|
||||
/// distance to occluder as well. However, just storing tons
|
||||
/// and tons of regular shadow maps (153 for a full day cycle, stored at
|
||||
/// irregular intervals) combined with clever explicit compression and
|
||||
/// avoiding recording sharp local shadows (preferring retracing for
|
||||
/// these), yielded a compression rate of under 3 bits per column! Since
|
||||
/// we likely want to avoid per-column shadows for worlds of the sizes we
|
||||
/// want, we'd still need to store *some* extra information to create
|
||||
/// soft shadows, but it would still be nice to try to drive down our
|
||||
/// size as much as possible given how compressible shadows of height
|
||||
/// maps seem to be in practice. Therefore, we try to take advantage of the
|
||||
/// way existing compression algorithms tend to work to see if we can
|
||||
/// achieve significant gains without doing a lot of custom work.
|
||||
///
|
||||
/// Specifically, since our rays are cast east/west, we expect that for each
|
||||
/// row, the horizon angles in each direction should be sequences of
|
||||
/// monotonically increasing values (as chunks approach a tall
|
||||
/// occluder), followed by sequences of no shadow, repeated
|
||||
/// until the end of the map. Monotonic sequences and same-byte sequences
|
||||
/// are usually easy to compress and existing algorithms are more likely
|
||||
/// to be able to deal with them than jumbled data. If we were to keep
|
||||
/// both directions in the same vector, off-the-shelf compression would
|
||||
/// probably be less effective.
|
||||
///
|
||||
/// For related reasons, rather than storing distances as in a standard
|
||||
/// distance map (which would lead to monotonically *decreasing* values
|
||||
/// as we approached the occluder from a given direction), we store the
|
||||
/// estimated *occluder height.* The idea here is that we replace the
|
||||
/// monotonic sequences with constant sequences, which are extremely
|
||||
/// straightforward to compress and mostly handled automatically by anything
|
||||
/// that does run-length encoding (i.e. most off-the-shelf compression
|
||||
/// algorithms).
|
||||
///
|
||||
/// We still need to benchmark this properly, as there's no guarantee our
|
||||
/// current compression algorithms will actually work well on this data
|
||||
/// in practice. It's possible that some other permutation (e.g. more
|
||||
/// bits reserved for "distance to occluder" in exchange for an even
|
||||
/// more predictible sequence) would end up compressing better than storing
|
||||
/// angles, or that we don't need as much precision as we currently have
|
||||
/// (256 possible angles).
|
||||
pub horizons: [(Vec<u8>, Vec<u8>); 2],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum InviteAnswer {
|
||||
Accepted,
|
||||
@ -192,80 +169,6 @@ pub enum DisconnectReason {
|
||||
Kicked(String),
|
||||
}
|
||||
|
||||
/// Messages sent from the server to the client
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ServerMsg {
|
||||
InitialSync {
|
||||
entity_package: sync::EntityPackage<EcsCompPacket>,
|
||||
server_info: ServerInfo,
|
||||
time_of_day: state::TimeOfDay,
|
||||
max_group_size: u32,
|
||||
client_timeout: Duration,
|
||||
world_map: WorldMapMsg,
|
||||
recipe_book: RecipeBook,
|
||||
},
|
||||
/// An error occurred while loading character data
|
||||
CharacterDataLoadError(String),
|
||||
/// A list of characters belonging to the a authenticated player was sent
|
||||
CharacterListUpdate(Vec<CharacterItem>),
|
||||
/// An error occurred while creating or deleting a character
|
||||
CharacterActionError(String),
|
||||
PlayerListUpdate(PlayerListUpdate),
|
||||
GroupUpdate(comp::group::ChangeNotification<sync::Uid>),
|
||||
// Indicate to the client that they are invited to join a group
|
||||
GroupInvite {
|
||||
inviter: sync::Uid,
|
||||
timeout: std::time::Duration,
|
||||
},
|
||||
// Indicate to the client that their sent invite was not invalid and is currently pending
|
||||
InvitePending(sync::Uid),
|
||||
// Note: this could potentially include all the failure cases such as inviting yourself in
|
||||
// which case the `InvitePending` message could be removed and the client could consider their
|
||||
// invite pending until they receive this message
|
||||
// Indicate to the client the result of their invite
|
||||
InviteComplete {
|
||||
target: sync::Uid,
|
||||
answer: InviteAnswer,
|
||||
},
|
||||
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
|
||||
/// Trigger cleanup for when the client goes back to the `Registered` state
|
||||
/// from an ingame state
|
||||
ExitIngameCleanup,
|
||||
Ping,
|
||||
Pong,
|
||||
/// A message to go into the client chat box. The client is responsible for
|
||||
/// formatting the message and turning it into a speech bubble.
|
||||
ChatMsg(comp::ChatMsg),
|
||||
SetPlayerEntity(Uid),
|
||||
TimeOfDay(state::TimeOfDay),
|
||||
EntitySync(sync::EntitySyncPackage),
|
||||
CompSync(sync::CompSyncPackage<EcsCompPacket>),
|
||||
CreateEntity(sync::EntityPackage<EcsCompPacket>),
|
||||
DeleteEntity(Uid),
|
||||
InventoryUpdate(comp::Inventory, comp::InventoryUpdateEvent),
|
||||
TerrainChunkUpdate {
|
||||
key: Vec2<i32>,
|
||||
chunk: Result<Box<TerrainChunk>, ()>,
|
||||
},
|
||||
TerrainBlockUpdates(HashMap<Vec3<i32>, Block>),
|
||||
Disconnect(DisconnectReason),
|
||||
TooManyPlayers,
|
||||
/// Send a popup notification such as "Waypoint Saved"
|
||||
Notification(Notification),
|
||||
SetViewDistance(u32),
|
||||
Outcomes(Vec<Outcome>),
|
||||
Knockback(Vec3<f32>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum RequestStateError {
|
||||
RegisterDenied(RegisterError),
|
||||
Denied,
|
||||
Already,
|
||||
Impossible,
|
||||
WrongMessage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum RegisterError {
|
||||
AlreadyLoggedIn,
|
||||
@ -276,10 +179,85 @@ pub enum RegisterError {
|
||||
//TODO: InvalidAlias,
|
||||
}
|
||||
|
||||
impl ServerMsg {
|
||||
pub fn verify(
|
||||
&self,
|
||||
c_type: ClientType,
|
||||
registered: bool,
|
||||
in_game: Option<super::ClientInGame>,
|
||||
) -> bool {
|
||||
match self {
|
||||
ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => {
|
||||
!registered && in_game.is_none()
|
||||
},
|
||||
ServerMsg::General(g) => {
|
||||
registered
|
||||
&& match g {
|
||||
//Character Screen related
|
||||
ServerGeneral::CharacterDataLoadError(_)
|
||||
| ServerGeneral::CharacterListUpdate(_)
|
||||
| ServerGeneral::CharacterActionError(_) => {
|
||||
c_type != ClientType::ChatOnly && in_game.is_none()
|
||||
},
|
||||
ServerGeneral::CharacterSuccess => {
|
||||
c_type == ClientType::Game && in_game.is_none()
|
||||
},
|
||||
//Ingame related
|
||||
ServerGeneral::GroupUpdate(_)
|
||||
| ServerGeneral::GroupInvite { .. }
|
||||
| ServerGeneral::InvitePending(_)
|
||||
| ServerGeneral::InviteComplete { .. }
|
||||
| ServerGeneral::ExitInGameSuccess
|
||||
| ServerGeneral::InventoryUpdate(_, _)
|
||||
| ServerGeneral::TerrainChunkUpdate { .. }
|
||||
| ServerGeneral::TerrainBlockUpdates(_)
|
||||
| ServerGeneral::SetViewDistance(_)
|
||||
| ServerGeneral::Outcomes(_)
|
||||
| ServerGeneral::Knockback(_) => {
|
||||
c_type == ClientType::Game && in_game.is_some()
|
||||
},
|
||||
// Always possible
|
||||
ServerGeneral::PlayerListUpdate(_)
|
||||
| ServerGeneral::ChatMsg(_)
|
||||
| ServerGeneral::SetPlayerEntity(_)
|
||||
| ServerGeneral::TimeOfDay(_)
|
||||
| ServerGeneral::EntitySync(_)
|
||||
| ServerGeneral::CompSync(_)
|
||||
| ServerGeneral::CreateEntity(_)
|
||||
| ServerGeneral::DeleteEntity(_)
|
||||
| ServerGeneral::Disconnect(_)
|
||||
| ServerGeneral::Notification(_) => true,
|
||||
}
|
||||
},
|
||||
ServerMsg::Ping(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuthClientError> for RegisterError {
|
||||
fn from(err: AuthClientError) -> Self { Self::AuthError(err.to_string()) }
|
||||
}
|
||||
|
||||
impl From<comp::ChatMsg> for ServerMsg {
|
||||
fn from(v: comp::ChatMsg) -> Self { ServerMsg::ChatMsg(v) }
|
||||
impl From<comp::ChatMsg> for ServerGeneral {
|
||||
fn from(v: comp::ChatMsg) -> Self { ServerGeneral::ChatMsg(v) }
|
||||
}
|
||||
|
||||
impl Into<ServerMsg> for ServerInfo {
|
||||
fn into(self) -> ServerMsg { ServerMsg::Info(self) }
|
||||
}
|
||||
|
||||
impl Into<ServerMsg> for ServerInit {
|
||||
fn into(self) -> ServerMsg { ServerMsg::Init(self) }
|
||||
}
|
||||
|
||||
impl Into<ServerMsg> for ServerRegisterAnswer {
|
||||
fn into(self) -> ServerMsg { ServerMsg::RegisterAnswer(self) }
|
||||
}
|
||||
|
||||
impl Into<ServerMsg> for ServerGeneral {
|
||||
fn into(self) -> ServerMsg { ServerMsg::General(self) }
|
||||
}
|
||||
|
||||
impl Into<ServerMsg> for PingMsg {
|
||||
fn into(self) -> ServerMsg { ServerMsg::Ping(self) }
|
||||
}
|
||||
|
123
common/src/msg/world_packet.rs
Normal file
123
common/src/msg/world_packet.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vek::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// World map information. Note that currently, we always send the whole thing
|
||||
/// in one go, but the structure aims to try to provide information as locally
|
||||
/// as possible, so that in the future we can split up large maps into multiple
|
||||
/// WorldMapMsg fragments.
|
||||
///
|
||||
/// TODO: Update message format to make fragmentable, allowing us to send more
|
||||
/// information without running into bandwidth issues.
|
||||
///
|
||||
/// TODO: Add information for rivers (currently, we just prerender them on the
|
||||
/// server, but this is not a great solution for LoD. The map rendering code is
|
||||
/// already set up to be able to take advantage of the river rendering being
|
||||
/// split out, but the format is a little complicated for space reasons and it
|
||||
/// may take some tweaking to get right, so we avoid sending it for now).
|
||||
///
|
||||
/// TODO: measure explicit compression schemes that might save space, e.g.
|
||||
/// repeating the "small angles" optimization that works well on more detailed
|
||||
/// shadow maps intended for height maps.
|
||||
pub struct WorldMapMsg {
|
||||
/// Log base 2 of world map dimensions (width × height) in chunks.
|
||||
///
|
||||
/// NOTE: Invariant: chunk count fits in a u16.
|
||||
pub dimensions_lg: Vec2<u32>,
|
||||
/// Sea level (used to provide a base altitude).
|
||||
pub sea_level: f32,
|
||||
/// Max height (used to scale altitudes).
|
||||
pub max_height: f32,
|
||||
/// RGB+A; the alpha channel is currently unused, but will be used in the
|
||||
/// future. Entries are in the usual chunk order.
|
||||
pub rgba: Vec<u32>,
|
||||
/// Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for
|
||||
/// altitude. The remainder are currently unused, but we have plans to
|
||||
/// use 7 bits for water depth (using an integer f7 encoding), and we
|
||||
/// will find other uses for the remaining 12 bits.
|
||||
pub alt: Vec<u32>,
|
||||
/// Horizon mapping. This is a variant of shadow mapping that is
|
||||
/// specifically designed for height maps; it takes advantage of their
|
||||
/// regular structure (e.g. no holes) to compress all information needed
|
||||
/// to decide when to cast a sharp shadow into a single nagle, the "horizon
|
||||
/// angle." This is the smallest angle with the ground at which light can
|
||||
/// pass through any occluders to reach the chunk, in some chosen
|
||||
/// horizontal direction. This would not be sufficient for a more
|
||||
/// complicated 3D structure, but it works for height maps since:
|
||||
///
|
||||
/// 1. they have no gaps, so as soon as light can shine through it will
|
||||
/// always be able to do so, and
|
||||
/// 2. we only care about lighting from the top, and only from the east and
|
||||
/// west (since at a large scale like this we mostly just want to
|
||||
/// handle variable sunlight; moonlight would present more challenges
|
||||
/// but we currently have no plans to try to cast accurate shadows in
|
||||
/// moonlight).
|
||||
///
|
||||
/// Our chosen format is two pairs of vectors,
|
||||
/// with the first pair representing west-facing light (casting shadows on
|
||||
/// the left side) and the second representing east-facing light
|
||||
/// (casting shadows on the east side).
|
||||
///
|
||||
/// The pair of vectors consists of (with each vector in the usual chunk
|
||||
/// order):
|
||||
///
|
||||
/// * Horizon angle pointing east (1 byte, scaled so 1 unit = 255° / 360).
|
||||
/// We might consider switching to tangent if that represents the
|
||||
/// information we care about better.
|
||||
/// * Approximate (floor) height of maximal occluder. We currently use this
|
||||
/// to try to deliver some approximation of soft shadows, which isn't that
|
||||
/// big a deal on the world map but is probably needed in order to ensure
|
||||
/// smooth transitions between chunks in LoD view. Additionally, when we
|
||||
/// start using the shadow information to do local lighting on the world
|
||||
/// map, we'll want a quick way to test where we can go out of shadow at
|
||||
/// arbitrary heights (since the player and other entities cajn find
|
||||
/// themselves far from the ground at times). While this is only an
|
||||
/// approximation to a proper distance map, hopefully it will give us
|
||||
/// something that feels reasonable enough for Veloren's style.
|
||||
///
|
||||
/// NOTE: On compression.
|
||||
///
|
||||
/// Horizon mapping has a lot of advantages for height maps (simple, easy to
|
||||
/// understand, doesn't require any fancy math or approximation beyond
|
||||
/// precision loss), though it loses a few of them by having to store
|
||||
/// distance to occluder as well. However, just storing tons
|
||||
/// and tons of regular shadow maps (153 for a full day cycle, stored at
|
||||
/// irregular intervals) combined with clever explicit compression and
|
||||
/// avoiding recording sharp local shadows (preferring retracing for
|
||||
/// these), yielded a compression rate of under 3 bits per column! Since
|
||||
/// we likely want to avoid per-column shadows for worlds of the sizes we
|
||||
/// want, we'd still need to store *some* extra information to create
|
||||
/// soft shadows, but it would still be nice to try to drive down our
|
||||
/// size as much as possible given how compressible shadows of height
|
||||
/// maps seem to be in practice. Therefore, we try to take advantage of the
|
||||
/// way existing compression algorithms tend to work to see if we can
|
||||
/// achieve significant gains without doing a lot of custom work.
|
||||
///
|
||||
/// Specifically, since our rays are cast east/west, we expect that for each
|
||||
/// row, the horizon angles in each direction should be sequences of
|
||||
/// monotonically increasing values (as chunks approach a tall
|
||||
/// occluder), followed by sequences of no shadow, repeated
|
||||
/// until the end of the map. Monotonic sequences and same-byte sequences
|
||||
/// are usually easy to compress and existing algorithms are more likely
|
||||
/// to be able to deal with them than jumbled data. If we were to keep
|
||||
/// both directions in the same vector, off-the-shelf compression would
|
||||
/// probably be less effective.
|
||||
///
|
||||
/// For related reasons, rather than storing distances as in a standard
|
||||
/// distance map (which would lead to monotonically *decreasing* values
|
||||
/// as we approached the occluder from a given direction), we store the
|
||||
/// estimated *occluder height.* The idea here is that we replace the
|
||||
/// monotonic sequences with constant sequences, which are extremely
|
||||
/// straightforward to compress and mostly handled automatically by anything
|
||||
/// that does run-length encoding (i.e. most off-the-shelf compression
|
||||
/// algorithms).
|
||||
///
|
||||
/// We still need to benchmark this properly, as there's no guarantee our
|
||||
/// current compression algorithms will actually work well on this data
|
||||
/// in practice. It's possible that some other permutation (e.g. more
|
||||
/// bits reserved for "distance to occluder" in exchange for an even
|
||||
/// more predictible sequence) would end up compressing better than storing
|
||||
/// angles, or that we don't need as much precision as we currently have
|
||||
/// (256 possible angles).
|
||||
pub horizons: [(Vec<u8>, Vec<u8>); 2],
|
||||
}
|
@ -22,6 +22,7 @@ uvth = "3.1.1"
|
||||
futures-util = "0.3"
|
||||
futures-executor = "0.3"
|
||||
futures-timer = "2.0"
|
||||
futures-channel = "0.3"
|
||||
itertools = "0.9"
|
||||
lazy_static = "1.4.0"
|
||||
scan_fmt = "0.2.4"
|
||||
|
@ -1,21 +1,24 @@
|
||||
use crate::error::Error;
|
||||
use common::msg::{ClientMsg, ClientState, RequestStateError, ServerMsg};
|
||||
use common::msg::{ClientInGame, ClientType, ServerGeneral, ServerMsg};
|
||||
use hashbrown::HashSet;
|
||||
use network::{Participant, Stream};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
use tracing::debug;
|
||||
use vek::*;
|
||||
|
||||
pub struct Client {
|
||||
pub client_state: ClientState,
|
||||
pub participant: Mutex<Option<Participant>>,
|
||||
pub singleton_stream: Stream,
|
||||
pub network_error: AtomicBool,
|
||||
pub registered: bool,
|
||||
pub client_type: ClientType,
|
||||
pub in_game: Option<ClientInGame>,
|
||||
pub participant: Option<Participant>,
|
||||
pub general_stream: Stream,
|
||||
pub ping_stream: Stream,
|
||||
pub register_stream: Stream,
|
||||
pub character_screen_stream: Stream,
|
||||
pub in_game_stream: Stream,
|
||||
pub network_error: bool,
|
||||
pub last_ping: f64,
|
||||
pub login_msg_sent: bool,
|
||||
}
|
||||
@ -25,22 +28,86 @@ impl Component for Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn notify(&mut self, msg: ServerMsg) {
|
||||
if !self.network_error.load(Ordering::Relaxed) {
|
||||
if let Err(e) = self.singleton_stream.send(msg) {
|
||||
fn internal_send<M: Serialize>(err: &mut bool, s: &mut Stream, msg: M) {
|
||||
if !*err {
|
||||
if let Err(e) = s.send(msg) {
|
||||
debug!(?e, "got a network error with client");
|
||||
self.network_error.store(true, Ordering::Relaxed);
|
||||
*err = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn recv(&mut self) -> Result<ClientMsg, Error> {
|
||||
if !self.network_error.load(Ordering::Relaxed) {
|
||||
match self.singleton_stream.recv().await {
|
||||
/*
|
||||
fn internal_send_raw(b: &AtomicBool, s: &mut Stream, msg: Arc<MessageBuffer>) {
|
||||
if !b.load(Ordering::Relaxed) {
|
||||
if let Err(e) = s.send_raw(msg) {
|
||||
debug!(?e, "got a network error with client");
|
||||
b.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn send_msg<S>(&mut self, msg: S)
|
||||
where
|
||||
S: Into<ServerMsg>,
|
||||
{
|
||||
const ERR: &str = "Dont do that, thats only done once at the start, no via this class";
|
||||
match msg.into() {
|
||||
ServerMsg::Info(_) => panic!(ERR),
|
||||
ServerMsg::Init(_) => panic!(ERR),
|
||||
ServerMsg::RegisterAnswer(msg) => {
|
||||
Self::internal_send(&mut self.network_error, &mut self.register_stream, &msg)
|
||||
},
|
||||
ServerMsg::General(msg) => {
|
||||
let stream = match &msg {
|
||||
//Character Screen related
|
||||
ServerGeneral::CharacterDataLoadError(_)
|
||||
| ServerGeneral::CharacterListUpdate(_)
|
||||
| ServerGeneral::CharacterActionError(_)
|
||||
| ServerGeneral::CharacterSuccess => &mut self.character_screen_stream,
|
||||
//Ingame related
|
||||
ServerGeneral::GroupUpdate(_)
|
||||
| ServerGeneral::GroupInvite { .. }
|
||||
| ServerGeneral::InvitePending(_)
|
||||
| ServerGeneral::InviteComplete { .. }
|
||||
| ServerGeneral::ExitInGameSuccess
|
||||
| ServerGeneral::InventoryUpdate(_, _)
|
||||
| ServerGeneral::TerrainChunkUpdate { .. }
|
||||
| ServerGeneral::TerrainBlockUpdates(_)
|
||||
| ServerGeneral::SetViewDistance(_)
|
||||
| ServerGeneral::Outcomes(_)
|
||||
| ServerGeneral::Knockback(_) => &mut self.in_game_stream,
|
||||
// Always possible
|
||||
ServerGeneral::PlayerListUpdate(_)
|
||||
| ServerGeneral::ChatMsg(_)
|
||||
| ServerGeneral::SetPlayerEntity(_)
|
||||
| ServerGeneral::TimeOfDay(_)
|
||||
| ServerGeneral::EntitySync(_)
|
||||
| ServerGeneral::CompSync(_)
|
||||
| ServerGeneral::CreateEntity(_)
|
||||
| ServerGeneral::DeleteEntity(_)
|
||||
| ServerGeneral::Disconnect(_)
|
||||
| ServerGeneral::Notification(_) => &mut self.general_stream,
|
||||
};
|
||||
Self::internal_send(&mut self.network_error, stream, &msg)
|
||||
},
|
||||
ServerMsg::Ping(msg) => {
|
||||
Self::internal_send(&mut self.network_error, &mut self.ping_stream, &msg)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn internal_recv<M: DeserializeOwned>(
|
||||
err: &mut bool,
|
||||
s: &mut Stream,
|
||||
) -> Result<M, Error> {
|
||||
if !*err {
|
||||
match s.recv().await {
|
||||
Ok(r) => Ok(r),
|
||||
Err(e) => {
|
||||
debug!(?e, "got a network error with client while recv");
|
||||
self.network_error.store(true, Ordering::Relaxed);
|
||||
*err = true;
|
||||
Err(Error::StreamErr(e))
|
||||
},
|
||||
}
|
||||
@ -48,31 +115,6 @@ impl Client {
|
||||
Err(Error::StreamErr(network::StreamError::StreamClosed))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_registered(&self) -> bool {
|
||||
matches!(
|
||||
self.client_state,
|
||||
ClientState::Registered | ClientState::Spectator | ClientState::Character
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_ingame(&self) -> bool {
|
||||
matches!(
|
||||
self.client_state,
|
||||
ClientState::Spectator | ClientState::Character
|
||||
)
|
||||
}
|
||||
|
||||
pub fn allow_state(&mut self, new_state: ClientState) {
|
||||
self.client_state = new_state;
|
||||
let _ = self
|
||||
.singleton_stream
|
||||
.send(ServerMsg::StateAnswer(Ok(new_state)));
|
||||
}
|
||||
|
||||
pub fn error_state(&mut self, error: RequestStateError) {
|
||||
let _ = self.notify(ServerMsg::StateAnswer(Err((error, self.client_state))));
|
||||
}
|
||||
}
|
||||
|
||||
// Distance from fuzzy_chunk before snapping to current chunk
|
||||
|
@ -12,7 +12,7 @@ use common::{
|
||||
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
|
||||
comp::{self, ChatType, Item, LightEmitter, WaypointArea},
|
||||
event::{EventBus, ServerEvent},
|
||||
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerMsg},
|
||||
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
|
||||
npc::{self, get_npc_name},
|
||||
state::TimeOfDay,
|
||||
sync::{Uid, WorldSyncExt},
|
||||
@ -504,8 +504,10 @@ fn handle_alias(
|
||||
ecs.read_storage::<comp::Player>().get(target),
|
||||
old_alias_optional,
|
||||
) {
|
||||
let msg =
|
||||
ServerMsg::PlayerListUpdate(PlayerListUpdate::Alias(*uid, player.alias.clone()));
|
||||
let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Alias(
|
||||
*uid,
|
||||
player.alias.clone(),
|
||||
));
|
||||
server.state.notify_registered_clients(msg);
|
||||
|
||||
// Announce alias change if target has a Body.
|
||||
@ -667,7 +669,9 @@ fn handle_spawn(
|
||||
.try_map(|e| uids.get(e).copied())
|
||||
.map(|g| (g, c))
|
||||
})
|
||||
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||
.map(|(g, c)| {
|
||||
c.send_msg(ServerGeneral::GroupUpdate(g))
|
||||
});
|
||||
},
|
||||
);
|
||||
} else if let Some(group) = match alignment {
|
||||
@ -1155,7 +1159,10 @@ fn handle_waypoint(
|
||||
.write_storage::<comp::Waypoint>()
|
||||
.insert(target, comp::Waypoint::new(pos.0, *time));
|
||||
server.notify_client(client, ChatType::CommandInfo.server_msg("Waypoint saved!"));
|
||||
server.notify_client(client, ServerMsg::Notification(Notification::WaypointSaved));
|
||||
server.notify_client(
|
||||
client,
|
||||
ServerGeneral::Notification(Notification::WaypointSaved),
|
||||
);
|
||||
},
|
||||
None => server.notify_client(
|
||||
client,
|
||||
@ -1191,7 +1198,7 @@ fn handle_adminify(
|
||||
ecs.write_storage().insert(player, comp::Admin).is_ok()
|
||||
};
|
||||
// Update player list so the player shows up as admin in client chat.
|
||||
let msg = ServerMsg::PlayerListUpdate(PlayerListUpdate::Admin(
|
||||
let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Admin(
|
||||
*ecs.read_storage::<Uid>()
|
||||
.get(player)
|
||||
.expect("Player should have uid"),
|
||||
@ -1584,7 +1591,7 @@ fn find_target(
|
||||
ecs: &specs::World,
|
||||
opt_alias: Option<String>,
|
||||
fallback: EcsEntity,
|
||||
) -> Result<EcsEntity, ServerMsg> {
|
||||
) -> Result<EcsEntity, ServerGeneral> {
|
||||
if let Some(alias) = opt_alias {
|
||||
(&ecs.entities(), &ecs.read_storage::<comp::Player>())
|
||||
.join()
|
||||
@ -1656,7 +1663,7 @@ fn handle_set_level(
|
||||
.expect("Failed to get uid for player");
|
||||
server
|
||||
.state
|
||||
.notify_registered_clients(ServerMsg::PlayerListUpdate(
|
||||
.notify_registered_clients(ServerGeneral::PlayerListUpdate(
|
||||
PlayerListUpdate::LevelChange(uid, lvl),
|
||||
));
|
||||
|
||||
@ -1896,7 +1903,7 @@ fn kick_player(server: &mut Server, target_player: EcsEntity, reason: &str) {
|
||||
.emit_now(ServerEvent::ClientDisconnect(target_player));
|
||||
server.notify_client(
|
||||
target_player,
|
||||
ServerMsg::Disconnect(DisconnectReason::Kicked(reason.to_string())),
|
||||
ServerGeneral::Disconnect(DisconnectReason::Kicked(reason.to_string())),
|
||||
);
|
||||
}
|
||||
|
||||
|
158
server/src/connection_handler.rs
Normal file
158
server/src/connection_handler.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use crate::{Client, ClientType, ServerInfo};
|
||||
use crossbeam::{bounded, unbounded, Receiver, Sender};
|
||||
use futures_channel::oneshot;
|
||||
use futures_executor::block_on;
|
||||
use futures_timer::Delay;
|
||||
use futures_util::{select, FutureExt};
|
||||
use network::{Network, Participant, Promises};
|
||||
use std::{sync::Arc, thread, time::Duration};
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
pub(crate) struct ServerInfoPacket {
|
||||
pub info: ServerInfo,
|
||||
pub time: f64,
|
||||
}
|
||||
|
||||
pub(crate) struct ConnectionHandler {
|
||||
_network: Arc<Network>,
|
||||
thread_handle: Option<thread::JoinHandle<()>>,
|
||||
pub client_receiver: Receiver<Client>,
|
||||
pub info_requester_receiver: Receiver<Sender<ServerInfoPacket>>,
|
||||
stop_sender: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
/// Instead of waiting the main loop we are handling connections, especially
|
||||
/// their slow network .await part on a different thread. We need to communicate
|
||||
/// to the Server main thread sometimes though to get the current server_info
|
||||
/// and time
|
||||
impl ConnectionHandler {
|
||||
pub fn new(network: Network) -> Self {
|
||||
let network = Arc::new(network);
|
||||
let network_clone = Arc::clone(&network);
|
||||
let (stop_sender, stop_receiver) = oneshot::channel();
|
||||
|
||||
let (client_sender, client_receiver) = unbounded::<Client>();
|
||||
let (info_requester_sender, info_requester_receiver) =
|
||||
bounded::<Sender<ServerInfoPacket>>(1);
|
||||
|
||||
let thread_handle = Some(thread::spawn(|| {
|
||||
block_on(Self::work(
|
||||
network_clone,
|
||||
client_sender,
|
||||
info_requester_sender,
|
||||
stop_receiver,
|
||||
));
|
||||
}));
|
||||
|
||||
Self {
|
||||
_network: network,
|
||||
thread_handle,
|
||||
client_receiver,
|
||||
info_requester_receiver,
|
||||
stop_sender: Some(stop_sender),
|
||||
}
|
||||
}
|
||||
|
||||
async fn work(
|
||||
network: Arc<Network>,
|
||||
client_sender: Sender<Client>,
|
||||
info_requester_sender: Sender<Sender<ServerInfoPacket>>,
|
||||
stop_receiver: oneshot::Receiver<()>,
|
||||
) {
|
||||
let mut stop_receiver = stop_receiver.fuse();
|
||||
loop {
|
||||
let participant = match select!(
|
||||
_ = stop_receiver => None,
|
||||
p = network.connected().fuse() => Some(p),
|
||||
) {
|
||||
None => break,
|
||||
Some(Ok(p)) => p,
|
||||
Some(Err(e)) => {
|
||||
error!(
|
||||
?e,
|
||||
"Stopping Conection Handler, no new connections can be made to server now!"
|
||||
);
|
||||
break;
|
||||
},
|
||||
};
|
||||
|
||||
let client_sender = client_sender.clone();
|
||||
let info_requester_sender = info_requester_sender.clone();
|
||||
|
||||
match select!(
|
||||
_ = stop_receiver => None,
|
||||
e = Self::init_participant(participant, client_sender, info_requester_sender).fuse() => Some(e),
|
||||
) {
|
||||
None => break,
|
||||
Some(Ok(())) => (),
|
||||
Some(Err(e)) => warn!(?e, "drop new participant, because an error occurred"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn init_participant(
|
||||
participant: Participant,
|
||||
client_sender: Sender<Client>,
|
||||
info_requester_sender: Sender<Sender<ServerInfoPacket>>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
debug!("New Participant connected to the server");
|
||||
let (sender, receiver) = bounded(1);
|
||||
info_requester_sender.send(sender)?;
|
||||
|
||||
let reliable = Promises::ORDERED | Promises::CONSISTENCY;
|
||||
let reliablec = reliable | Promises::COMPRESSED;
|
||||
|
||||
let general_stream = participant.open(10, reliablec).await?;
|
||||
let ping_stream = participant.open(5, reliable).await?;
|
||||
let mut register_stream = participant.open(10, reliablec).await?;
|
||||
let character_screen_stream = participant.open(10, reliablec).await?;
|
||||
let in_game_stream = participant.open(10, reliablec).await?;
|
||||
|
||||
let server_data = receiver.recv()?;
|
||||
|
||||
register_stream.send(server_data.info)?;
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_secs(5);
|
||||
let client_type = match select!(
|
||||
_ = Delay::new(TIMEOUT).fuse() => None,
|
||||
t = register_stream.recv::<ClientType>().fuse() => Some(t),
|
||||
) {
|
||||
None => {
|
||||
debug!("slow client connection detected, dropping it");
|
||||
return Ok(());
|
||||
},
|
||||
Some(client_type) => client_type?,
|
||||
};
|
||||
|
||||
let client = Client {
|
||||
registered: false,
|
||||
client_type,
|
||||
in_game: None,
|
||||
participant: Some(participant),
|
||||
general_stream,
|
||||
ping_stream,
|
||||
register_stream,
|
||||
in_game_stream,
|
||||
character_screen_stream,
|
||||
network_error: false,
|
||||
last_ping: server_data.time,
|
||||
login_msg_sent: false,
|
||||
};
|
||||
|
||||
client_sender.send(client)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConnectionHandler {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.stop_sender.take().unwrap().send(());
|
||||
trace!("blocking till ConnectionHandler is closed");
|
||||
self.thread_handle
|
||||
.take()
|
||||
.unwrap()
|
||||
.join()
|
||||
.expect("There was an error in ConnectionHandler, clean shutdown impossible");
|
||||
trace!("gracefully closed ConnectionHandler!");
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ use common::{
|
||||
Player, Pos, Stats,
|
||||
},
|
||||
lottery::Lottery,
|
||||
msg::{PlayerListUpdate, ServerMsg},
|
||||
msg::{PlayerListUpdate, ServerGeneral},
|
||||
outcome::Outcome,
|
||||
state::BlockChange,
|
||||
sync::{Uid, UidAllocator, WorldSyncExt},
|
||||
@ -44,7 +44,7 @@ pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3<f32>)
|
||||
}
|
||||
let mut clients = state.ecs().write_storage::<Client>();
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(ServerMsg::Knockback(impulse));
|
||||
client.send_msg(ServerGeneral::Knockback(impulse));
|
||||
}
|
||||
}
|
||||
|
||||
@ -656,7 +656,7 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) {
|
||||
|
||||
server
|
||||
.state
|
||||
.notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(
|
||||
*uid, new_level,
|
||||
)));
|
||||
.notify_registered_clients(ServerGeneral::PlayerListUpdate(
|
||||
PlayerListUpdate::LevelChange(*uid, new_level),
|
||||
));
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use common::{
|
||||
group::{self, Group, GroupManager, Invite, PendingInvites},
|
||||
ChatType, GroupManip,
|
||||
},
|
||||
msg::{InviteAnswer, ServerMsg},
|
||||
msg::{InviteAnswer, ServerGeneral},
|
||||
sync,
|
||||
sync::WorldSyncExt,
|
||||
};
|
||||
@ -31,7 +31,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
None => {
|
||||
// Inform of failure
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta
|
||||
.server_msg("Invite failed, target does not exist.".to_owned()),
|
||||
);
|
||||
@ -63,7 +63,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
if already_in_same_group {
|
||||
// Inform of failure
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(ChatType::Meta.server_msg(
|
||||
client.send_msg(ChatType::Meta.server_msg(
|
||||
"Invite failed, can't invite someone already in your group".to_owned(),
|
||||
));
|
||||
}
|
||||
@ -93,7 +93,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
if group_size_limit_reached {
|
||||
// Inform inviter that they have reached the group size limit
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta.server_msg(
|
||||
"Invite failed, pending invites plus current group size have reached \
|
||||
the group size limit"
|
||||
@ -110,7 +110,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
if invites.contains(invitee) {
|
||||
// Inform inviter that there is already an invite
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta
|
||||
.server_msg("This player already has a pending invite.".to_owned()),
|
||||
);
|
||||
@ -155,7 +155,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
(clients.get_mut(invitee), uids.get(entity).copied())
|
||||
{
|
||||
if send_invite() {
|
||||
client.notify(ServerMsg::GroupInvite {
|
||||
client.send_msg(ServerGeneral::GroupInvite {
|
||||
inviter,
|
||||
timeout: PRESENTED_INVITE_TIMEOUT_DUR,
|
||||
});
|
||||
@ -163,7 +163,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
} else if agents.contains(invitee) {
|
||||
send_invite();
|
||||
} else if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta.server_msg("Can't invite, not a player or npc".to_owned()),
|
||||
);
|
||||
}
|
||||
@ -171,7 +171,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
// Notify inviter that the invite is pending
|
||||
if invite_sent {
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(ServerMsg::InvitePending(uid));
|
||||
client.send_msg(ServerGeneral::InvitePending(uid));
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -196,7 +196,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
if let (Some(client), Some(target)) =
|
||||
(clients.get_mut(inviter), uids.get(entity).copied())
|
||||
{
|
||||
client.notify(ServerMsg::InviteComplete {
|
||||
client.send_msg(ServerGeneral::InviteComplete {
|
||||
target,
|
||||
answer: InviteAnswer::Accepted,
|
||||
})
|
||||
@ -217,7 +217,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
.try_map(|e| uids.get(e).copied())
|
||||
.map(|g| (g, c))
|
||||
})
|
||||
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||
.map(|(g, c)| c.send_msg(ServerGeneral::GroupUpdate(g)));
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -244,7 +244,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
if let (Some(client), Some(target)) =
|
||||
(clients.get_mut(inviter), uids.get(entity).copied())
|
||||
{
|
||||
client.notify(ServerMsg::InviteComplete {
|
||||
client.send_msg(ServerGeneral::InviteComplete {
|
||||
target,
|
||||
answer: InviteAnswer::Declined,
|
||||
})
|
||||
@ -269,7 +269,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
.try_map(|e| uids.get(e).copied())
|
||||
.map(|g| (g, c))
|
||||
})
|
||||
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||
.map(|(g, c)| c.send_msg(ServerGeneral::GroupUpdate(g)));
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -283,7 +283,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
None => {
|
||||
// Inform of failure
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta
|
||||
.server_msg("Kick failed, target does not exist.".to_owned()),
|
||||
);
|
||||
@ -296,7 +296,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
if matches!(alignments.get(target), Some(comp::Alignment::Owned(owner)) if uids.get(target).map_or(true, |u| u != owner))
|
||||
{
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta.server_msg("Kick failed, you can't kick pets.".to_owned()),
|
||||
);
|
||||
}
|
||||
@ -305,7 +305,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
// Can't kick yourself
|
||||
if uids.get(entity).map_or(false, |u| *u == uid) {
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta
|
||||
.server_msg("Kick failed, you can't kick yourself.".to_owned()),
|
||||
);
|
||||
@ -336,26 +336,26 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
.try_map(|e| uids.get(e).copied())
|
||||
.map(|g| (g, c))
|
||||
})
|
||||
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||
.map(|(g, c)| c.send_msg(ServerGeneral::GroupUpdate(g)));
|
||||
},
|
||||
);
|
||||
|
||||
// Tell them the have been kicked
|
||||
if let Some(client) = clients.get_mut(target) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta
|
||||
.server_msg("You were removed from the group.".to_owned()),
|
||||
);
|
||||
}
|
||||
// Tell kicker that they were succesful
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(ChatType::Meta.server_msg("Player kicked.".to_owned()));
|
||||
client.send_msg(ChatType::Meta.server_msg("Player kicked.".to_owned()));
|
||||
}
|
||||
},
|
||||
Some(_) => {
|
||||
// Inform kicker that they are not the leader
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(ChatType::Meta.server_msg(
|
||||
client.send_msg(ChatType::Meta.server_msg(
|
||||
"Kick failed: You are not the leader of the target's group.".to_owned(),
|
||||
));
|
||||
}
|
||||
@ -363,7 +363,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
None => {
|
||||
// Inform kicker that the target is not in a group
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta.server_msg(
|
||||
"Kick failed: Your target is not in a group.".to_owned(),
|
||||
),
|
||||
@ -380,7 +380,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
None => {
|
||||
// Inform of failure
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(ChatType::Meta.server_msg(
|
||||
client.send_msg(ChatType::Meta.server_msg(
|
||||
"Leadership transfer failed, target does not exist".to_owned(),
|
||||
));
|
||||
}
|
||||
@ -410,18 +410,18 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
.try_map(|e| uids.get(e).copied())
|
||||
.map(|g| (g, c))
|
||||
})
|
||||
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||
.map(|(g, c)| c.send_msg(ServerGeneral::GroupUpdate(g)));
|
||||
},
|
||||
);
|
||||
// Tell them they are the leader
|
||||
if let Some(client) = clients.get_mut(target) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta.server_msg("You are the group leader now.".to_owned()),
|
||||
);
|
||||
}
|
||||
// Tell the old leader that the transfer was succesful
|
||||
if let Some(client) = clients.get_mut(target) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta
|
||||
.server_msg("You are no longer the group leader.".to_owned()),
|
||||
);
|
||||
@ -430,7 +430,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
Some(_) => {
|
||||
// Inform transferer that they are not the leader
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(
|
||||
client.send_msg(
|
||||
ChatType::Meta.server_msg(
|
||||
"Transfer failed: You are not the leader of the target's group."
|
||||
.to_owned(),
|
||||
@ -441,7 +441,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
|
||||
None => {
|
||||
// Inform transferer that the target is not in a group
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(ChatType::Meta.server_msg(
|
||||
client.send_msg(ChatType::Meta.server_msg(
|
||||
"Transfer failed: Your target is not in a group.".to_owned(),
|
||||
));
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
comp::{self, item},
|
||||
msg::ServerMsg,
|
||||
msg::ServerGeneral,
|
||||
sync::{Uid, WorldSyncExt},
|
||||
};
|
||||
use specs::{world::WorldExt, Entity as EcsEntity};
|
||||
@ -116,7 +116,7 @@ pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) {
|
||||
let mut clients = ecs.write_storage::<Client>();
|
||||
if clients.get_mut(possesse).is_none() {
|
||||
if let Some(mut client) = clients.remove(possessor) {
|
||||
client.notify(ServerMsg::SetPlayerEntity(possesse_uid));
|
||||
client.send_msg(ServerGeneral::SetPlayerEntity(possesse_uid));
|
||||
clients
|
||||
.insert(possesse, client)
|
||||
.err()
|
||||
|
@ -5,7 +5,7 @@ use common::{
|
||||
slot::{self, Slot},
|
||||
Pos, MAX_PICKUP_RANGE_SQR,
|
||||
},
|
||||
msg::ServerMsg,
|
||||
msg::ServerGeneral,
|
||||
recipe::default_recipe_book,
|
||||
sync::{Uid, WorldSyncExt},
|
||||
vol::ReadVol,
|
||||
@ -281,7 +281,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
.map(|g| (g, c))
|
||||
})
|
||||
.map(|(g, c)| {
|
||||
c.notify(ServerMsg::GroupUpdate(g))
|
||||
c.send_msg(ServerGeneral::GroupUpdate(g))
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
use common::{
|
||||
comp,
|
||||
comp::{group, Player},
|
||||
msg::{ClientState, PlayerListUpdate, ServerMsg},
|
||||
msg::{PlayerListUpdate, ServerGeneral},
|
||||
span,
|
||||
sync::{Uid, UidAllocator},
|
||||
};
|
||||
@ -33,9 +33,8 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
|
||||
.cloned();
|
||||
if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) {
|
||||
// Tell client its request was successful
|
||||
client.allow_state(ClientState::Registered);
|
||||
// Tell client to clear out other entities and its own components
|
||||
client.notify(ServerMsg::ExitIngameCleanup);
|
||||
client.in_game = None;
|
||||
client.send_msg(ServerGeneral::ExitInGameSuccess);
|
||||
|
||||
let entity_builder = state.ecs_mut().create_entity().with(client).with(player);
|
||||
|
||||
@ -90,14 +89,13 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
|
||||
|
||||
pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event {
|
||||
span!(_guard, "handle_client_disconnect");
|
||||
if let Some(client) = server.state().read_storage::<Client>().get(entity) {
|
||||
let participant = match client.participant.try_lock() {
|
||||
Ok(mut p) => p.take().unwrap(),
|
||||
Err(e) => {
|
||||
error!(?e, ?entity, "couldn't lock participant for removal");
|
||||
return Event::ClientDisconnected { entity };
|
||||
},
|
||||
};
|
||||
if let Some(client) = server
|
||||
.state()
|
||||
.ecs()
|
||||
.write_storage::<Client>()
|
||||
.get_mut(entity)
|
||||
{
|
||||
let participant = client.participant.take().unwrap();
|
||||
let pid = participant.remote_pid();
|
||||
std::thread::spawn(move || {
|
||||
let span = tracing::span!(tracing::Level::DEBUG, "client_disconnect", ?pid, ?entity);
|
||||
@ -131,8 +129,9 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event
|
||||
) {
|
||||
state.notify_registered_clients(comp::ChatType::Offline(*uid).server_msg(""));
|
||||
|
||||
state
|
||||
.notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove(*uid)));
|
||||
state.notify_registered_clients(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(
|
||||
*uid,
|
||||
)));
|
||||
}
|
||||
|
||||
// Make sure to remove the player from the logged in list. (See LoginProvider)
|
||||
|
@ -9,6 +9,7 @@ mod character_creator;
|
||||
pub mod chunk_generator;
|
||||
pub mod client;
|
||||
pub mod cmd;
|
||||
pub mod connection_handler;
|
||||
mod data_dir;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
@ -35,6 +36,7 @@ use crate::{
|
||||
chunk_generator::ChunkGenerator,
|
||||
client::{Client, RegionSubscription},
|
||||
cmd::ChatCommandExt,
|
||||
connection_handler::ConnectionHandler,
|
||||
data_dir::DataDir,
|
||||
login_provider::LoginProvider,
|
||||
state_ext::StateExt,
|
||||
@ -44,7 +46,9 @@ use common::{
|
||||
cmd::ChatCommand,
|
||||
comp::{self, ChatType},
|
||||
event::{EventBus, ServerEvent},
|
||||
msg::{server::WorldMapMsg, ClientState, DisconnectReason, ServerInfo, ServerMsg},
|
||||
msg::{
|
||||
ClientType, DisconnectReason, ServerGeneral, ServerInfo, ServerInit, ServerMsg, WorldMapMsg,
|
||||
},
|
||||
outcome::Outcome,
|
||||
recipe::default_recipe_book,
|
||||
state::{State, TimeOfDay},
|
||||
@ -53,8 +57,6 @@ use common::{
|
||||
vol::{ReadVol, RectVolSize},
|
||||
};
|
||||
use futures_executor::block_on;
|
||||
use futures_timer::Delay;
|
||||
use futures_util::{select, FutureExt};
|
||||
use metrics::{ServerMetrics, StateTickMetrics, TickMetrics};
|
||||
use network::{Network, Pid, ProtocolAddr};
|
||||
use persistence::{
|
||||
@ -70,7 +72,7 @@ use std::{
|
||||
};
|
||||
#[cfg(not(feature = "worldgen"))]
|
||||
use test_world::{IndexOwned, World};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing::{debug, error, info, trace};
|
||||
use uvth::{ThreadPool, ThreadPoolBuilder};
|
||||
use vek::*;
|
||||
#[cfg(feature = "worldgen")]
|
||||
@ -97,7 +99,7 @@ pub struct Server {
|
||||
index: IndexOwned,
|
||||
map: WorldMapMsg,
|
||||
|
||||
network: Network,
|
||||
connection_handler: ConnectionHandler,
|
||||
|
||||
thread_pool: ThreadPool,
|
||||
|
||||
@ -332,6 +334,7 @@ impl Server {
|
||||
.expect("Failed to initialize server metrics submodule.");
|
||||
thread_pool.execute(f);
|
||||
block_on(network.listen(ProtocolAddr::Tcp(settings.gameserver_address)))?;
|
||||
let connection_handler = ConnectionHandler::new(network);
|
||||
|
||||
let this = Self {
|
||||
state,
|
||||
@ -339,7 +342,7 @@ impl Server {
|
||||
index,
|
||||
map,
|
||||
|
||||
network,
|
||||
connection_handler,
|
||||
|
||||
thread_pool,
|
||||
|
||||
@ -449,7 +452,7 @@ impl Server {
|
||||
let before_new_connections = Instant::now();
|
||||
|
||||
// 3) Handle inputs from clients
|
||||
block_on(self.handle_new_connections(&mut frontend_events))?;
|
||||
self.handle_new_connections(&mut frontend_events)?;
|
||||
|
||||
let before_message_system = Instant::now();
|
||||
|
||||
@ -523,11 +526,11 @@ impl Server {
|
||||
CharacterLoaderResponseType::CharacterList(result) => match result {
|
||||
Ok(character_list_data) => self.notify_client(
|
||||
query_result.entity,
|
||||
ServerMsg::CharacterListUpdate(character_list_data),
|
||||
ServerGeneral::CharacterListUpdate(character_list_data),
|
||||
),
|
||||
Err(error) => self.notify_client(
|
||||
query_result.entity,
|
||||
ServerMsg::CharacterActionError(error.to_string()),
|
||||
ServerGeneral::CharacterActionError(error.to_string()),
|
||||
),
|
||||
},
|
||||
CharacterLoaderResponseType::CharacterData(result) => {
|
||||
@ -542,7 +545,7 @@ impl Server {
|
||||
// to display
|
||||
self.notify_client(
|
||||
query_result.entity,
|
||||
ServerMsg::CharacterDataLoadError(error.to_string()),
|
||||
ServerGeneral::CharacterDataLoadError(error.to_string()),
|
||||
);
|
||||
|
||||
// Clean up the entity data on the server
|
||||
@ -791,88 +794,65 @@ impl Server {
|
||||
}
|
||||
|
||||
/// Handle new client connections.
|
||||
async fn handle_new_connections(
|
||||
&mut self,
|
||||
frontend_events: &mut Vec<Event>,
|
||||
) -> Result<(), Error> {
|
||||
//TIMEOUT 0.1 ms for msg handling
|
||||
const TIMEOUT: Duration = Duration::from_micros(100);
|
||||
const SLOWLORIS_TIMEOUT: Duration = Duration::from_millis(300);
|
||||
loop {
|
||||
let participant = match select!(
|
||||
_ = Delay::new(TIMEOUT).fuse() => None,
|
||||
pr = self.network.connected().fuse() => Some(pr),
|
||||
) {
|
||||
None => return Ok(()),
|
||||
Some(pr) => pr?,
|
||||
};
|
||||
debug!("New Participant connected to the server");
|
||||
fn handle_new_connections(&mut self, frontend_events: &mut Vec<Event>) -> Result<(), Error> {
|
||||
while let Ok(sender) = self.connection_handler.info_requester_receiver.try_recv() {
|
||||
// can fail, e.g. due to timeout or network prob.
|
||||
trace!("sending info to connection_handler");
|
||||
let _ = sender.send(crate::connection_handler::ServerInfoPacket {
|
||||
info: self.get_server_info(),
|
||||
time: self.state.get_time(),
|
||||
});
|
||||
}
|
||||
|
||||
let singleton_stream = match select!(
|
||||
_ = Delay::new(SLOWLORIS_TIMEOUT).fuse() => None,
|
||||
sr = participant.opened().fuse() => Some(sr),
|
||||
) {
|
||||
None => {
|
||||
warn!("Either Slowloris attack or very slow client, dropping");
|
||||
return Ok(()); //return rather then continue to give removes a tick more to send data.
|
||||
},
|
||||
Some(Ok(s)) => s,
|
||||
Some(Err(e)) => {
|
||||
warn!(?e, "Failed to open a Stream from remote client. dropping");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
let mut client = Client {
|
||||
client_state: ClientState::Connected,
|
||||
participant: std::sync::Mutex::new(Some(participant)),
|
||||
singleton_stream,
|
||||
network_error: std::sync::atomic::AtomicBool::new(false),
|
||||
last_ping: self.state.get_time(),
|
||||
login_msg_sent: false,
|
||||
};
|
||||
while let Ok(data) = self.connection_handler.client_receiver.try_recv() {
|
||||
let mut client = data;
|
||||
|
||||
if self.settings().max_players
|
||||
<= self.state.ecs().read_storage::<Client>().join().count()
|
||||
{
|
||||
// Note: in this case the client is dropped
|
||||
client.notify(ServerMsg::TooManyPlayers);
|
||||
} else {
|
||||
let entity = self
|
||||
.state
|
||||
.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.with(client)
|
||||
.build();
|
||||
self.state
|
||||
.ecs()
|
||||
.read_resource::<metrics::PlayerMetrics>()
|
||||
.clients_connected
|
||||
.inc();
|
||||
// Send client all the tracked components currently attached to its entity as
|
||||
// well as synced resources (currently only `TimeOfDay`)
|
||||
debug!("Starting initial sync with client.");
|
||||
self.state
|
||||
.ecs()
|
||||
.write_storage::<Client>()
|
||||
.get_mut(entity)
|
||||
.unwrap()
|
||||
.notify(ServerMsg::InitialSync {
|
||||
// Send client their entity
|
||||
entity_package: TrackedComps::fetch(&self.state.ecs())
|
||||
.create_entity_package(entity, None, None, None),
|
||||
server_info: self.get_server_info(),
|
||||
time_of_day: *self.state.ecs().read_resource(),
|
||||
max_group_size: self.settings().max_player_group_size,
|
||||
client_timeout: self.settings().client_timeout,
|
||||
world_map: self.map.clone(),
|
||||
recipe_book: (&*default_recipe_book()).clone(),
|
||||
});
|
||||
|
||||
frontend_events.push(Event::ClientConnected { entity });
|
||||
debug!("Done initial sync with client.");
|
||||
trace!(
|
||||
?client.participant,
|
||||
"to many players, wont allow participant to connect"
|
||||
);
|
||||
client.register_stream.send(ServerInit::TooManyPlayers)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let entity = self
|
||||
.state
|
||||
.ecs_mut()
|
||||
.create_entity_synced()
|
||||
.with(client)
|
||||
.build();
|
||||
self.state
|
||||
.ecs()
|
||||
.read_resource::<metrics::PlayerMetrics>()
|
||||
.clients_connected
|
||||
.inc();
|
||||
// Send client all the tracked components currently attached to its entity as
|
||||
// well as synced resources (currently only `TimeOfDay`)
|
||||
debug!("Starting initial sync with client.");
|
||||
self.state
|
||||
.ecs()
|
||||
.write_storage::<Client>()
|
||||
.get_mut(entity)
|
||||
.unwrap()
|
||||
.register_stream
|
||||
.send(ServerInit::GameSync {
|
||||
// Send client their entity
|
||||
entity_package: TrackedComps::fetch(&self.state.ecs())
|
||||
.create_entity_package(entity, None, None, None),
|
||||
time_of_day: *self.state.ecs().read_resource(),
|
||||
max_group_size: self.settings().max_player_group_size,
|
||||
client_timeout: self.settings().client_timeout,
|
||||
world_map: self.map.clone(),
|
||||
recipe_book: (&*default_recipe_book()).clone(),
|
||||
})?;
|
||||
|
||||
frontend_events.push(Event::ClientConnected { entity });
|
||||
debug!("Done initial sync with client.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn notify_client<S>(&self, entity: EcsEntity, msg: S)
|
||||
@ -880,11 +860,11 @@ impl Server {
|
||||
S: Into<ServerMsg>,
|
||||
{
|
||||
if let Some(client) = self.state.ecs().write_storage::<Client>().get_mut(entity) {
|
||||
client.notify(msg.into())
|
||||
client.send_msg(msg.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_registered_clients(&mut self, msg: ServerMsg) {
|
||||
pub fn notify_registered_clients(&mut self, msg: ServerGeneral) {
|
||||
self.state.notify_registered_clients(msg);
|
||||
}
|
||||
|
||||
@ -964,7 +944,7 @@ impl Server {
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
self.state
|
||||
.notify_registered_clients(ServerMsg::Disconnect(DisconnectReason::Shutdown));
|
||||
.notify_registered_clients(ServerGeneral::Disconnect(DisconnectReason::Shutdown));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ use common::{
|
||||
character::CharacterId,
|
||||
comp,
|
||||
effect::Effect,
|
||||
msg::{CharacterInfo, ClientState, PlayerListUpdate, ServerMsg},
|
||||
msg::{CharacterInfo, ClientInGame, PlayerListUpdate, ServerGeneral, ServerMsg},
|
||||
state::State,
|
||||
sync::{Uid, UidAllocator, WorldSyncExt},
|
||||
util::Dir,
|
||||
@ -59,7 +59,8 @@ pub trait StateExt {
|
||||
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
|
||||
/// Iterates over registered clients and send each `ServerMsg`
|
||||
fn send_chat(&self, msg: comp::UnresolvedChatMsg);
|
||||
fn notify_registered_clients(&self, msg: ServerMsg);
|
||||
fn notify_registered_clients(&self, msg: ServerGeneral);
|
||||
fn notify_in_game_clients(&self, msg: ServerGeneral);
|
||||
/// Delete an entity, recording the deletion in [`DeletedEntities`]
|
||||
fn delete_entity_recorded(
|
||||
&mut self,
|
||||
@ -216,7 +217,8 @@ impl StateExt for State {
|
||||
|
||||
// Tell the client its request was successful.
|
||||
if let Some(client) = self.ecs().write_storage::<Client>().get_mut(entity) {
|
||||
client.allow_state(ClientState::Character);
|
||||
client.in_game = Some(ClientInGame::Character);
|
||||
client.send_msg(ServerGeneral::CharacterSuccess)
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,7 +227,7 @@ impl StateExt for State {
|
||||
|
||||
if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
|
||||
// Notify clients of a player list update
|
||||
self.notify_registered_clients(ServerMsg::PlayerListUpdate(
|
||||
self.notify_registered_clients(ServerGeneral::PlayerListUpdate(
|
||||
PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
|
||||
name: String::from(&stats.name),
|
||||
level: stats.level.level(),
|
||||
@ -272,7 +274,7 @@ impl StateExt for State {
|
||||
| comp::ChatType::Kill(_, _)
|
||||
| comp::ChatType::Meta
|
||||
| comp::ChatType::World(_) => {
|
||||
self.notify_registered_clients(ServerMsg::ChatMsg(resolved_msg))
|
||||
self.notify_registered_clients(ServerGeneral::ChatMsg(resolved_msg))
|
||||
},
|
||||
comp::ChatType::Tell(u, t) => {
|
||||
for (client, uid) in (
|
||||
@ -282,7 +284,7 @@ impl StateExt for State {
|
||||
.join()
|
||||
{
|
||||
if uid == u || uid == t {
|
||||
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -294,7 +296,7 @@ impl StateExt for State {
|
||||
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
|
||||
if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) {
|
||||
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -306,7 +308,7 @@ impl StateExt for State {
|
||||
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
|
||||
if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) {
|
||||
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -318,7 +320,7 @@ impl StateExt for State {
|
||||
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
|
||||
if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) {
|
||||
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -332,7 +334,7 @@ impl StateExt for State {
|
||||
.join()
|
||||
{
|
||||
if s == &faction.0 {
|
||||
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -344,7 +346,7 @@ impl StateExt for State {
|
||||
.join()
|
||||
{
|
||||
if g == group {
|
||||
client.notify(ServerMsg::ChatMsg(resolved_msg.clone()));
|
||||
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -352,12 +354,24 @@ impl StateExt for State {
|
||||
}
|
||||
|
||||
/// Sends the message to all connected clients
|
||||
fn notify_registered_clients(&self, msg: ServerMsg) {
|
||||
fn notify_registered_clients(&self, msg: ServerGeneral) {
|
||||
let msg: ServerMsg = msg.into();
|
||||
for client in (&mut self.ecs().write_storage::<Client>())
|
||||
.join()
|
||||
.filter(|c| c.is_registered())
|
||||
.filter(|c| c.registered)
|
||||
{
|
||||
client.notify(msg.clone());
|
||||
client.send_msg(msg.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends the message to all clients playing in game
|
||||
fn notify_in_game_clients(&self, msg: ServerGeneral) {
|
||||
let msg: ServerMsg = msg.into();
|
||||
for client in (&mut self.ecs().write_storage::<Client>())
|
||||
.join()
|
||||
.filter(|c| c.in_game.is_some())
|
||||
{
|
||||
client.send_msg(msg.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@ -384,7 +398,7 @@ impl StateExt for State {
|
||||
.try_map(|e| uids.get(e).copied())
|
||||
.map(|g| (g, c))
|
||||
})
|
||||
.map(|(g, c)| c.notify(ServerMsg::GroupUpdate(g)));
|
||||
.map(|(g, c)| c.send_msg(ServerGeneral::GroupUpdate(g)));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel},
|
||||
msg::ServerMsg,
|
||||
msg::ServerGeneral,
|
||||
outcome::Outcome,
|
||||
region::{Event as RegionEvent, RegionMap},
|
||||
span,
|
||||
@ -107,7 +107,7 @@ impl<'a> System<'a> for Sys {
|
||||
let mut subscribers = (&mut clients, &entities, &subscriptions, &positions)
|
||||
.join()
|
||||
.filter_map(|(client, entity, subscription, pos)| {
|
||||
if client.is_ingame() && subscription.regions.contains(&key) {
|
||||
if client.in_game.is_some() && subscription.regions.contains(&key) {
|
||||
Some((client, &subscription.regions, entity, *pos))
|
||||
} else {
|
||||
None
|
||||
@ -129,7 +129,7 @@ impl<'a> System<'a> for Sys {
|
||||
})
|
||||
}) {
|
||||
let create_msg =
|
||||
ServerMsg::CreateEntity(tracked_comps.create_entity_package(
|
||||
ServerGeneral::CreateEntity(tracked_comps.create_entity_package(
|
||||
entity,
|
||||
Some(*pos),
|
||||
vel.copied(),
|
||||
@ -143,7 +143,7 @@ impl<'a> System<'a> for Sys {
|
||||
// Client doesn't need to know about itself
|
||||
&& *client_entity != entity
|
||||
{
|
||||
client.notify(create_msg.clone());
|
||||
client.send_msg(create_msg.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -157,7 +157,7 @@ impl<'a> System<'a> for Sys {
|
||||
.map(|key| !regions.contains(key))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
client.notify(ServerMsg::DeleteEntity(uid));
|
||||
client.send_msg(ServerGeneral::DeleteEntity(uid));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -174,14 +174,14 @@ impl<'a> System<'a> for Sys {
|
||||
.take_deleted_in_region(key)
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
let entity_sync_msg = ServerMsg::EntitySync(entity_sync_package);
|
||||
let comp_sync_msg = ServerMsg::CompSync(comp_sync_package);
|
||||
let entity_sync_msg = ServerGeneral::EntitySync(entity_sync_package);
|
||||
let comp_sync_msg = ServerGeneral::CompSync(comp_sync_package);
|
||||
subscribers.iter_mut().for_each(move |(client, _, _, _)| {
|
||||
client.notify(entity_sync_msg.clone());
|
||||
client.notify(comp_sync_msg.clone());
|
||||
client.send_msg(entity_sync_msg.clone());
|
||||
client.send_msg(comp_sync_msg.clone());
|
||||
});
|
||||
|
||||
let mut send_msg = |msg: ServerMsg,
|
||||
let mut send_msg = |msg: ServerGeneral,
|
||||
entity: EcsEntity,
|
||||
pos: Pos,
|
||||
force_update: Option<&ForceUpdate>,
|
||||
@ -212,7 +212,7 @@ impl<'a> System<'a> for Sys {
|
||||
true // Closer than 100 blocks
|
||||
}
|
||||
} {
|
||||
client.notify(msg.clone());
|
||||
client.send_msg(msg.clone());
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -287,7 +287,7 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
|
||||
send_msg(
|
||||
ServerMsg::CompSync(comp_sync_package),
|
||||
ServerGeneral::CompSync(comp_sync_package),
|
||||
entity,
|
||||
pos,
|
||||
force_update,
|
||||
@ -303,7 +303,7 @@ impl<'a> System<'a> for Sys {
|
||||
(&mut clients, &subscriptions)
|
||||
.join()
|
||||
.filter_map(|(client, subscription)| {
|
||||
if client.is_ingame() && subscription.regions.contains(®ion_key) {
|
||||
if client.in_game.is_some() && subscription.regions.contains(®ion_key) {
|
||||
Some(client)
|
||||
} else {
|
||||
None
|
||||
@ -311,7 +311,7 @@ impl<'a> System<'a> for Sys {
|
||||
})
|
||||
{
|
||||
for uid in &deleted {
|
||||
client.notify(ServerMsg::DeleteEntity(Uid(*uid)));
|
||||
client.send_msg(ServerGeneral::DeleteEntity(Uid(*uid)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -320,7 +320,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Sync inventories
|
||||
for (client, inventory, update) in (&mut clients, &inventories, &inventory_updates).join() {
|
||||
client.notify(ServerMsg::InventoryUpdate(
|
||||
client.send_msg(ServerGeneral::InventoryUpdate(
|
||||
inventory.clone(),
|
||||
update.event(),
|
||||
));
|
||||
@ -341,7 +341,7 @@ impl<'a> System<'a> for Sys {
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if !outcomes.is_empty() {
|
||||
client.notify(ServerMsg::Outcomes(outcomes));
|
||||
client.send_msg(ServerGeneral::Outcomes(outcomes));
|
||||
}
|
||||
}
|
||||
outcomes.clear();
|
||||
@ -353,9 +353,9 @@ impl<'a> System<'a> for Sys {
|
||||
// Sync resources
|
||||
// TODO: doesn't really belong in this system (rename system or create another
|
||||
// system?)
|
||||
let tof_msg = ServerMsg::TimeOfDay(*time_of_day);
|
||||
let tof_msg = ServerGeneral::TimeOfDay(*time_of_day);
|
||||
for client in (&mut clients).join() {
|
||||
client.notify(tof_msg.clone());
|
||||
client.send_msg(tof_msg.clone());
|
||||
}
|
||||
|
||||
timer.end();
|
||||
|
@ -2,7 +2,7 @@ use super::SysTimer;
|
||||
use crate::client::Client;
|
||||
use common::{
|
||||
comp::group::{Invite, PendingInvites},
|
||||
msg::{InviteAnswer, ServerMsg},
|
||||
msg::{InviteAnswer, ServerGeneral},
|
||||
span,
|
||||
sync::Uid,
|
||||
};
|
||||
@ -54,7 +54,7 @@ impl<'a> System<'a> for Sys {
|
||||
if let (Some(client), Some(target)) =
|
||||
(clients.get_mut(*inviter), uids.get(invitee).copied())
|
||||
{
|
||||
client.notify(ServerMsg::InviteComplete {
|
||||
client.send_msg(ServerGeneral::InviteComplete {
|
||||
target,
|
||||
answer: InviteAnswer::TimedOut,
|
||||
})
|
||||
|
@ -15,9 +15,9 @@ use common::{
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
msg::{
|
||||
validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientMsg, ClientState,
|
||||
DisconnectReason, PlayerInfo, PlayerListUpdate, RequestStateError, ServerMsg,
|
||||
MAX_BYTES_CHAT_MSG,
|
||||
validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientGeneral, ClientInGame,
|
||||
ClientRegister, DisconnectReason, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError,
|
||||
ServerGeneral, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG,
|
||||
},
|
||||
span,
|
||||
state::{BlockChange, Time},
|
||||
@ -32,13 +32,392 @@ use hashbrown::HashMap;
|
||||
use specs::{
|
||||
Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteExpect, WriteStorage,
|
||||
};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
impl Sys {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_client_msg(
|
||||
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
||||
new_chat_msgs: &mut Vec<(Option<specs::Entity>, UnresolvedChatMsg)>,
|
||||
entity: specs::Entity,
|
||||
client: &mut Client,
|
||||
player_metrics: &ReadExpect<'_, PlayerMetrics>,
|
||||
uids: &ReadStorage<'_, Uid>,
|
||||
chat_modes: &ReadStorage<'_, ChatMode>,
|
||||
msg: ClientGeneral,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
match msg {
|
||||
ClientGeneral::ChatMsg(message) => {
|
||||
if client.registered {
|
||||
match validate_chat_msg(&message) {
|
||||
Ok(()) => {
|
||||
if let Some(from) = uids.get(entity) {
|
||||
let mode = chat_modes.get(entity).cloned().unwrap_or_default();
|
||||
let msg = mode.new_message(*from, message);
|
||||
new_chat_msgs.push((Some(entity), msg));
|
||||
} else {
|
||||
error!("Could not send message. Missing player uid");
|
||||
}
|
||||
},
|
||||
Err(ChatMsgValidationError::TooLong) => {
|
||||
let max = MAX_BYTES_CHAT_MSG;
|
||||
let len = message.len();
|
||||
warn!(?len, ?max, "Received a chat message that's too long")
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::Disconnect => {
|
||||
client.send_msg(ServerGeneral::Disconnect(DisconnectReason::Requested));
|
||||
},
|
||||
ClientGeneral::Terminate => {
|
||||
debug!(?entity, "Client send message to termitate session");
|
||||
player_metrics
|
||||
.clients_disconnected
|
||||
.with_label_values(&["gracefully"])
|
||||
.inc();
|
||||
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
|
||||
},
|
||||
_ => unreachable!("not a client_general msg"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_client_in_game_msg(
|
||||
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
||||
entity: specs::Entity,
|
||||
client: &mut Client,
|
||||
terrain: &ReadExpect<'_, TerrainGrid>,
|
||||
network_metrics: &ReadExpect<'_, NetworkRequestMetrics>,
|
||||
can_build: &ReadStorage<'_, CanBuild>,
|
||||
force_updates: &ReadStorage<'_, ForceUpdate>,
|
||||
stats: &mut WriteStorage<'_, Stats>,
|
||||
block_changes: &mut Write<'_, BlockChange>,
|
||||
positions: &mut WriteStorage<'_, Pos>,
|
||||
velocities: &mut WriteStorage<'_, Vel>,
|
||||
orientations: &mut WriteStorage<'_, Ori>,
|
||||
players: &mut WriteStorage<'_, Player>,
|
||||
controllers: &mut WriteStorage<'_, Controller>,
|
||||
settings: &Read<'_, Settings>,
|
||||
msg: ClientGeneral,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
if client.in_game.is_none() {
|
||||
debug!(?entity, "client is not in_game, ignoring msg");
|
||||
trace!(?msg, "ignored msg content");
|
||||
if matches!(msg, ClientGeneral::TerrainChunkRequest{ .. }) {
|
||||
network_metrics.chunks_request_dropped.inc();
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
match msg {
|
||||
// Go back to registered state (char selection screen)
|
||||
ClientGeneral::ExitInGame => {
|
||||
client.in_game = None;
|
||||
server_emitter.emit(ServerEvent::ExitIngame { entity });
|
||||
client.send_msg(ServerGeneral::ExitInGameSuccess);
|
||||
},
|
||||
ClientGeneral::SetViewDistance(view_distance) => {
|
||||
players.get_mut(entity).map(|player| {
|
||||
player.view_distance = Some(
|
||||
settings
|
||||
.max_view_distance
|
||||
.map(|max| view_distance.min(max))
|
||||
.unwrap_or(view_distance),
|
||||
)
|
||||
});
|
||||
|
||||
//correct client if its VD is to high
|
||||
if settings
|
||||
.max_view_distance
|
||||
.map(|max| view_distance > max)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
client.send_msg(ServerGeneral::SetViewDistance(
|
||||
settings.max_view_distance.unwrap_or(0),
|
||||
));
|
||||
}
|
||||
},
|
||||
ClientGeneral::ControllerInputs(inputs) => {
|
||||
if let Some(ClientInGame::Character) = client.in_game {
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
controller.inputs.update_with_new(inputs);
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::ControlEvent(event) => {
|
||||
if let Some(ClientInGame::Character) = client.in_game {
|
||||
// Skip respawn if client entity is alive
|
||||
if let ControlEvent::Respawn = event {
|
||||
if stats.get(entity).map_or(true, |s| !s.is_dead) {
|
||||
//Todo: comment why return!
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
controller.events.push(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::ControlAction(event) => {
|
||||
if let Some(ClientInGame::Character) = client.in_game {
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
controller.actions.push(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::PlayerPhysics { pos, vel, ori } => {
|
||||
if let Some(ClientInGame::Character) = client.in_game {
|
||||
if force_updates.get(entity).is_none()
|
||||
&& stats.get(entity).map_or(true, |s| !s.is_dead)
|
||||
{
|
||||
let _ = positions.insert(entity, pos);
|
||||
let _ = velocities.insert(entity, vel);
|
||||
let _ = orientations.insert(entity, ori);
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientGeneral::BreakBlock(pos) => {
|
||||
if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) {
|
||||
block_changes.set(pos, block.into_vacant());
|
||||
}
|
||||
},
|
||||
ClientGeneral::PlaceBlock(pos, block) => {
|
||||
if can_build.get(entity).is_some() {
|
||||
block_changes.try_set(pos, block);
|
||||
}
|
||||
},
|
||||
ClientGeneral::TerrainChunkRequest { key } => {
|
||||
let in_vd = if let (Some(view_distance), Some(pos)) = (
|
||||
players.get(entity).and_then(|p| p.view_distance),
|
||||
positions.get(entity),
|
||||
) {
|
||||
pos.0.xy().map(|e| e as f64).distance(
|
||||
key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
|
||||
) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt())
|
||||
* TerrainChunkSize::RECT_SIZE.x as f64
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if in_vd {
|
||||
match terrain.get_key(key) {
|
||||
Some(chunk) => {
|
||||
network_metrics.chunks_served_from_memory.inc();
|
||||
client.send_msg(ServerGeneral::TerrainChunkUpdate {
|
||||
key,
|
||||
chunk: Ok(Box::new(chunk.clone())),
|
||||
})
|
||||
},
|
||||
None => {
|
||||
network_metrics.chunks_generation_triggered.inc();
|
||||
server_emitter.emit(ServerEvent::ChunkRequest(entity, key))
|
||||
},
|
||||
}
|
||||
} else {
|
||||
network_metrics.chunks_request_dropped.inc();
|
||||
}
|
||||
},
|
||||
ClientGeneral::UnlockSkill(skill) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.unlock_skill(skill));
|
||||
},
|
||||
ClientGeneral::RefundSkill(skill) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.refund_skill(skill));
|
||||
},
|
||||
ClientGeneral::UnlockSkillGroup(skill_group_type) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.unlock_skill_group(skill_group_type));
|
||||
},
|
||||
_ => unreachable!("not a client_in_game msg"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_client_character_screen_msg(
|
||||
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
||||
new_chat_msgs: &mut Vec<(Option<specs::Entity>, UnresolvedChatMsg)>,
|
||||
entity: specs::Entity,
|
||||
client: &mut Client,
|
||||
character_loader: &ReadExpect<'_, CharacterLoader>,
|
||||
uids: &ReadStorage<'_, Uid>,
|
||||
players: &mut WriteStorage<'_, Player>,
|
||||
editable_settings: &ReadExpect<'_, EditableSettings>,
|
||||
alias_validator: &ReadExpect<'_, AliasValidator>,
|
||||
msg: ClientGeneral,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
match msg {
|
||||
// Request spectator state
|
||||
ClientGeneral::Spectate if client.registered => {
|
||||
client.in_game = Some(ClientInGame::Spectator)
|
||||
},
|
||||
ClientGeneral::Spectate => debug!("dropped Spectate msg from unregistered client"),
|
||||
ClientGeneral::Character(character_id)
|
||||
if client.registered && client.in_game.is_none() =>
|
||||
{
|
||||
if let Some(player) = players.get(entity) {
|
||||
// Send a request to load the character's component data from the
|
||||
// DB. Once loaded, persisted components such as stats and inventory
|
||||
// will be inserted for the entity
|
||||
character_loader.load_character_data(
|
||||
entity,
|
||||
player.uuid().to_string(),
|
||||
character_id,
|
||||
);
|
||||
|
||||
// Start inserting non-persisted/default components for the entity
|
||||
// while we load the DB data
|
||||
server_emitter.emit(ServerEvent::InitCharacterData {
|
||||
entity,
|
||||
character_id,
|
||||
});
|
||||
|
||||
// Give the player a welcome message
|
||||
if !editable_settings.server_description.is_empty() {
|
||||
client.send_msg(
|
||||
ChatType::CommandInfo
|
||||
.server_msg(String::from(&*editable_settings.server_description)),
|
||||
);
|
||||
}
|
||||
|
||||
if !client.login_msg_sent {
|
||||
if let Some(player_uid) = uids.get(entity) {
|
||||
new_chat_msgs.push((None, UnresolvedChatMsg {
|
||||
chat_type: ChatType::Online(*player_uid),
|
||||
message: "".to_string(),
|
||||
}));
|
||||
|
||||
client.login_msg_sent = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
client.send_msg(ServerGeneral::CharacterDataLoadError(String::from(
|
||||
"Failed to fetch player entity",
|
||||
)))
|
||||
}
|
||||
}
|
||||
ClientGeneral::Character(_) => {
|
||||
let registered = client.registered;
|
||||
let in_game = client.in_game;
|
||||
debug!(?registered, ?in_game, "dropped Character msg from client");
|
||||
},
|
||||
ClientGeneral::RequestCharacterList => {
|
||||
if let Some(player) = players.get(entity) {
|
||||
character_loader.load_character_list(entity, player.uuid().to_string())
|
||||
}
|
||||
},
|
||||
ClientGeneral::CreateCharacter { alias, tool, body } => {
|
||||
if let Err(error) = alias_validator.validate(&alias) {
|
||||
debug!(?error, ?alias, "denied alias as it contained a banned word");
|
||||
client.send_msg(ServerGeneral::CharacterActionError(error.to_string()));
|
||||
} else if let Some(player) = players.get(entity) {
|
||||
character_creator::create_character(
|
||||
entity,
|
||||
player.uuid().to_string(),
|
||||
alias,
|
||||
tool,
|
||||
body,
|
||||
character_loader,
|
||||
);
|
||||
}
|
||||
},
|
||||
ClientGeneral::DeleteCharacter(character_id) => {
|
||||
if let Some(player) = players.get(entity) {
|
||||
character_loader.delete_character(
|
||||
entity,
|
||||
player.uuid().to_string(),
|
||||
character_id,
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => unreachable!("not a client_character_screen msg"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_ping_msg(client: &mut Client, msg: PingMsg) -> Result<(), crate::error::Error> {
|
||||
match msg {
|
||||
PingMsg::Ping => client.send_msg(PingMsg::Pong),
|
||||
PingMsg::Pong => {},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn handle_register_msg(
|
||||
player_list: &HashMap<Uid, PlayerInfo>,
|
||||
new_players: &mut Vec<specs::Entity>,
|
||||
entity: specs::Entity,
|
||||
client: &mut Client,
|
||||
player_metrics: &ReadExpect<'_, PlayerMetrics>,
|
||||
login_provider: &mut WriteExpect<'_, LoginProvider>,
|
||||
admins: &mut WriteStorage<'_, Admin>,
|
||||
players: &mut WriteStorage<'_, Player>,
|
||||
editable_settings: &ReadExpect<'_, EditableSettings>,
|
||||
msg: ClientRegister,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
let (username, uuid) = match login_provider.try_login(
|
||||
&msg.token_or_username,
|
||||
&*editable_settings.admins,
|
||||
&*editable_settings.whitelist,
|
||||
&*editable_settings.banlist,
|
||||
) {
|
||||
Err(err) => {
|
||||
client
|
||||
.register_stream
|
||||
.send(ServerRegisterAnswer::Err(err))?;
|
||||
return Ok(());
|
||||
},
|
||||
Ok((username, uuid)) => (username, uuid),
|
||||
};
|
||||
|
||||
const INITIAL_VD: Option<u32> = Some(5); //will be changed after login
|
||||
let player = Player::new(username, None, INITIAL_VD, uuid);
|
||||
let is_admin = editable_settings.admins.contains(&uuid);
|
||||
|
||||
if !player.is_valid() {
|
||||
// Invalid player
|
||||
client
|
||||
.register_stream
|
||||
.send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !client.registered && client.in_game.is_none() {
|
||||
// Add Player component to this client
|
||||
let _ = players.insert(entity, player);
|
||||
player_metrics.players_connected.inc();
|
||||
|
||||
// Give the Admin component to the player if their name exists in
|
||||
// admin list
|
||||
if is_admin {
|
||||
let _ = admins.insert(entity, Admin);
|
||||
}
|
||||
|
||||
// Tell the client its request was successful.
|
||||
client.registered = true;
|
||||
client.register_stream.send(ServerRegisterAnswer::Ok(()))?;
|
||||
|
||||
// Send initial player list
|
||||
client.send_msg(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init(
|
||||
player_list.clone(),
|
||||
)));
|
||||
|
||||
// Add to list to notify all clients of the new player
|
||||
new_players.push(entity);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///We needed to move this to a async fn, if we would use a async closures
|
||||
/// the compiler generates to much recursion and fails to compile this
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_client_msg(
|
||||
async fn handle_messages(
|
||||
server_emitter: &mut common::event::Emitter<'_, ServerEvent>,
|
||||
new_chat_msgs: &mut Vec<(Option<specs::Entity>, UnresolvedChatMsg)>,
|
||||
player_list: &HashMap<Uid, PlayerInfo>,
|
||||
@ -64,357 +443,103 @@ impl Sys {
|
||||
players: &mut WriteStorage<'_, Player>,
|
||||
controllers: &mut WriteStorage<'_, Controller>,
|
||||
settings: &Read<'_, Settings>,
|
||||
editable_settings: &ReadExpect<'_, EditableSettings>,
|
||||
alias_validator: &ReadExpect<'_, AliasValidator>,
|
||||
editable_settings: &EditableSettings,
|
||||
) -> Result<(), crate::error::Error> {
|
||||
let (mut b1, mut b2, mut b3, mut b4, mut b5) = (
|
||||
client.network_error,
|
||||
client.network_error,
|
||||
client.network_error,
|
||||
client.network_error,
|
||||
client.network_error,
|
||||
);
|
||||
loop {
|
||||
let msg = client.recv().await?;
|
||||
/*
|
||||
waiting for 1 of the 5 streams to return a massage asynchronous.
|
||||
If so, handle that msg type. This code will be refactored soon
|
||||
*/
|
||||
|
||||
let q1 = Client::internal_recv(&mut b1, &mut client.general_stream);
|
||||
let q2 = Client::internal_recv(&mut b2, &mut client.in_game_stream);
|
||||
let q3 = Client::internal_recv(&mut b3, &mut client.character_screen_stream);
|
||||
let q4 = Client::internal_recv(&mut b4, &mut client.ping_stream);
|
||||
let q5 = Client::internal_recv(&mut b5, &mut client.register_stream);
|
||||
|
||||
let (m1, m2, m3, m4, m5) = select!(
|
||||
msg = q1.fuse() => (Some(msg), None, None, None, None),
|
||||
msg = q2.fuse() => (None, Some(msg), None, None, None),
|
||||
msg = q3.fuse() => (None, None, Some(msg), None, None),
|
||||
msg = q4.fuse() => (None, None, None, Some(msg), None),
|
||||
msg = q5.fuse() => (None, None, None, None,Some(msg)),
|
||||
);
|
||||
*cnt += 1;
|
||||
match msg {
|
||||
// Go back to registered state (char selection screen)
|
||||
ClientMsg::ExitIngame => match client.client_state {
|
||||
// Use ClientMsg::Register instead.
|
||||
ClientState::Connected => client.error_state(RequestStateError::WrongMessage),
|
||||
ClientState::Registered => client.error_state(RequestStateError::Already),
|
||||
ClientState::Spectator | ClientState::Character => {
|
||||
server_emitter.emit(ServerEvent::ExitIngame { entity });
|
||||
},
|
||||
ClientState::Pending => {},
|
||||
},
|
||||
// Request spectator state
|
||||
ClientMsg::Spectate => match client.client_state {
|
||||
// Become Registered first.
|
||||
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
||||
ClientState::Spectator => client.error_state(RequestStateError::Already),
|
||||
ClientState::Registered | ClientState::Character => {
|
||||
client.allow_state(ClientState::Spectator)
|
||||
},
|
||||
ClientState::Pending => {},
|
||||
},
|
||||
// Request registered state (login)
|
||||
ClientMsg::Register {
|
||||
view_distance,
|
||||
token_or_username,
|
||||
} => {
|
||||
let (username, uuid) = match login_provider.try_login(
|
||||
&token_or_username,
|
||||
&*editable_settings.admins,
|
||||
&*editable_settings.whitelist,
|
||||
&*editable_settings.banlist,
|
||||
) {
|
||||
Err(err) => {
|
||||
client.error_state(RequestStateError::RegisterDenied(err));
|
||||
break Ok(());
|
||||
},
|
||||
Ok((username, uuid)) => (username, uuid),
|
||||
};
|
||||
|
||||
let vd =
|
||||
view_distance.map(|vd| vd.min(settings.max_view_distance.unwrap_or(vd)));
|
||||
let is_admin = editable_settings.admins.contains(&uuid);
|
||||
let player = Player::new(username.clone(), None, vd, uuid);
|
||||
|
||||
if !player.is_valid() {
|
||||
// Invalid player
|
||||
client.error_state(RequestStateError::Impossible);
|
||||
break Ok(());
|
||||
}
|
||||
|
||||
match client.client_state {
|
||||
ClientState::Connected => {
|
||||
// Add Player component to this client
|
||||
let _ = players.insert(entity, player);
|
||||
player_metrics.players_connected.inc();
|
||||
|
||||
// Give the Admin component to the player if their name exists in
|
||||
// admin list
|
||||
if is_admin {
|
||||
let _ = admins.insert(entity, Admin);
|
||||
}
|
||||
|
||||
// Tell the client its request was successful.
|
||||
client.allow_state(ClientState::Registered);
|
||||
|
||||
// Send initial player list
|
||||
client.notify(ServerMsg::PlayerListUpdate(PlayerListUpdate::Init(
|
||||
player_list.clone(),
|
||||
)));
|
||||
|
||||
// Add to list to notify all clients of the new player
|
||||
new_players.push(entity);
|
||||
},
|
||||
// Use RequestState instead (No need to send `player` again).
|
||||
_ => client.error_state(RequestStateError::Impossible),
|
||||
}
|
||||
//client.allow_state(ClientState::Registered);
|
||||
|
||||
// Limit view distance if it's too high
|
||||
// This comes after state registration so that the client actually hears it
|
||||
if settings
|
||||
.max_view_distance
|
||||
.zip(view_distance)
|
||||
.map(|(max, vd)| vd > max)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
client.notify(ServerMsg::SetViewDistance(
|
||||
settings.max_view_distance.unwrap_or(0),
|
||||
));
|
||||
};
|
||||
},
|
||||
ClientMsg::SetViewDistance(view_distance) => {
|
||||
if let ClientState::Character { .. } = client.client_state {
|
||||
players.get_mut(entity).map(|player| {
|
||||
player.view_distance = Some(
|
||||
settings
|
||||
.max_view_distance
|
||||
.map(|max| view_distance.min(max))
|
||||
.unwrap_or(view_distance),
|
||||
)
|
||||
});
|
||||
|
||||
if settings
|
||||
.max_view_distance
|
||||
.map(|max| view_distance > max)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
client.notify(ServerMsg::SetViewDistance(
|
||||
settings.max_view_distance.unwrap_or(0),
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientMsg::Character(character_id) => match client.client_state {
|
||||
// Become Registered first.
|
||||
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
||||
ClientState::Registered | ClientState::Spectator => {
|
||||
// Only send login message if it wasn't already
|
||||
// sent previously
|
||||
if let Some(player) = players.get(entity) {
|
||||
// Send a request to load the character's component data from the
|
||||
// DB. Once loaded, persisted components such as stats and inventory
|
||||
// will be inserted for the entity
|
||||
character_loader.load_character_data(
|
||||
entity,
|
||||
player.uuid().to_string(),
|
||||
character_id,
|
||||
);
|
||||
|
||||
// Start inserting non-persisted/default components for the entity
|
||||
// while we load the DB data
|
||||
server_emitter.emit(ServerEvent::InitCharacterData {
|
||||
entity,
|
||||
character_id,
|
||||
});
|
||||
|
||||
// Give the player a welcome message
|
||||
if !editable_settings.server_description.is_empty() {
|
||||
client.notify(ChatType::CommandInfo.server_msg(String::from(
|
||||
&*editable_settings.server_description,
|
||||
)));
|
||||
}
|
||||
|
||||
// Only send login message if it wasn't already
|
||||
// sent previously
|
||||
if !client.login_msg_sent {
|
||||
if let Some(player_uid) = uids.get(entity) {
|
||||
new_chat_msgs.push((None, UnresolvedChatMsg {
|
||||
chat_type: ChatType::Online(*player_uid),
|
||||
message: "".to_string(),
|
||||
}));
|
||||
|
||||
client.login_msg_sent = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
client.notify(ServerMsg::CharacterDataLoadError(String::from(
|
||||
"Failed to fetch player entity",
|
||||
)))
|
||||
}
|
||||
},
|
||||
ClientState::Character => client.error_state(RequestStateError::Already),
|
||||
ClientState::Pending => {},
|
||||
},
|
||||
ClientMsg::ControllerInputs(inputs) => match client.client_state {
|
||||
ClientState::Connected | ClientState::Registered | ClientState::Spectator => {
|
||||
client.error_state(RequestStateError::Impossible)
|
||||
},
|
||||
ClientState::Character => {
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
controller.inputs.update_with_new(inputs);
|
||||
}
|
||||
},
|
||||
ClientState::Pending => {},
|
||||
},
|
||||
ClientMsg::ControlEvent(event) => match client.client_state {
|
||||
ClientState::Connected | ClientState::Registered | ClientState::Spectator => {
|
||||
client.error_state(RequestStateError::Impossible)
|
||||
},
|
||||
ClientState::Character => {
|
||||
// Skip respawn if client entity is alive
|
||||
if let ControlEvent::Respawn = event {
|
||||
if stats.get(entity).map_or(true, |s| !s.is_dead) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
controller.events.push(event);
|
||||
}
|
||||
},
|
||||
ClientState::Pending => {},
|
||||
},
|
||||
ClientMsg::ControlAction(event) => match client.client_state {
|
||||
ClientState::Connected | ClientState::Registered | ClientState::Spectator => {
|
||||
client.error_state(RequestStateError::Impossible)
|
||||
},
|
||||
ClientState::Character => {
|
||||
if let Some(controller) = controllers.get_mut(entity) {
|
||||
controller.actions.push(event);
|
||||
}
|
||||
},
|
||||
ClientState::Pending => {},
|
||||
},
|
||||
ClientMsg::ChatMsg(message) => match client.client_state {
|
||||
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
||||
ClientState::Registered | ClientState::Spectator | ClientState::Character => {
|
||||
match validate_chat_msg(&message) {
|
||||
Ok(()) => {
|
||||
if let Some(from) = uids.get(entity) {
|
||||
let mode = chat_modes.get(entity).cloned().unwrap_or_default();
|
||||
let msg = mode.new_message(*from, message);
|
||||
new_chat_msgs.push((Some(entity), msg));
|
||||
} else {
|
||||
error!("Could not send message. Missing player uid");
|
||||
}
|
||||
},
|
||||
Err(ChatMsgValidationError::TooLong) => {
|
||||
let max = MAX_BYTES_CHAT_MSG;
|
||||
let len = message.len();
|
||||
warn!(?len, ?max, "Received a chat message that's too long")
|
||||
},
|
||||
}
|
||||
},
|
||||
ClientState::Pending => {},
|
||||
},
|
||||
ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state {
|
||||
ClientState::Character => {
|
||||
if force_updates.get(entity).is_none()
|
||||
&& stats.get(entity).map_or(true, |s| !s.is_dead)
|
||||
{
|
||||
let _ = positions.insert(entity, pos);
|
||||
let _ = velocities.insert(entity, vel);
|
||||
let _ = orientations.insert(entity, ori);
|
||||
}
|
||||
},
|
||||
// Only characters can send positions.
|
||||
_ => client.error_state(RequestStateError::Impossible),
|
||||
},
|
||||
ClientMsg::BreakBlock(pos) => {
|
||||
if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) {
|
||||
block_changes.set(pos, block.into_vacant());
|
||||
}
|
||||
},
|
||||
ClientMsg::PlaceBlock(pos, block) => {
|
||||
if can_build.get(entity).is_some() {
|
||||
block_changes.try_set(pos, block);
|
||||
}
|
||||
},
|
||||
ClientMsg::TerrainChunkRequest { key } => match client.client_state {
|
||||
ClientState::Connected | ClientState::Registered => {
|
||||
network_metrics.chunks_request_dropped.inc();
|
||||
client.error_state(RequestStateError::Impossible);
|
||||
},
|
||||
ClientState::Spectator | ClientState::Character => {
|
||||
let in_vd = if let (Some(view_distance), Some(pos)) = (
|
||||
players.get(entity).and_then(|p| p.view_distance),
|
||||
positions.get(entity),
|
||||
) {
|
||||
pos.0.xy().map(|e| e as f64).distance(
|
||||
key.map(|e| e as f64 + 0.5)
|
||||
* TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
|
||||
) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt())
|
||||
* TerrainChunkSize::RECT_SIZE.x as f64
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if in_vd {
|
||||
match terrain.get_key(key) {
|
||||
Some(chunk) => {
|
||||
network_metrics.chunks_served_from_memory.inc();
|
||||
client.notify(ServerMsg::TerrainChunkUpdate {
|
||||
key,
|
||||
chunk: Ok(Box::new(chunk.clone())),
|
||||
})
|
||||
},
|
||||
None => {
|
||||
network_metrics.chunks_generation_triggered.inc();
|
||||
server_emitter.emit(ServerEvent::ChunkRequest(entity, key))
|
||||
},
|
||||
}
|
||||
} else {
|
||||
network_metrics.chunks_request_dropped.inc();
|
||||
}
|
||||
},
|
||||
ClientState::Pending => {},
|
||||
},
|
||||
// Always possible.
|
||||
ClientMsg::Ping => client.notify(ServerMsg::Pong),
|
||||
ClientMsg::Pong => {},
|
||||
ClientMsg::Disconnect => {
|
||||
client.notify(ServerMsg::Disconnect(DisconnectReason::Requested));
|
||||
},
|
||||
ClientMsg::Terminate => {
|
||||
debug!(?entity, "Client send message to termitate session");
|
||||
player_metrics
|
||||
.clients_disconnected
|
||||
.with_label_values(&["gracefully"])
|
||||
.inc();
|
||||
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
|
||||
break Ok(());
|
||||
},
|
||||
ClientMsg::RequestCharacterList => {
|
||||
if let Some(player) = players.get(entity) {
|
||||
character_loader.load_character_list(entity, player.uuid().to_string())
|
||||
}
|
||||
},
|
||||
ClientMsg::CreateCharacter { alias, tool, body } => {
|
||||
if let Err(error) = alias_validator.validate(&alias) {
|
||||
debug!(?error, ?alias, "denied alias as it contained a banned word");
|
||||
client.notify(ServerMsg::CharacterActionError(error.to_string()));
|
||||
} else if let Some(player) = players.get(entity) {
|
||||
character_creator::create_character(
|
||||
entity,
|
||||
player.uuid().to_string(),
|
||||
alias,
|
||||
tool,
|
||||
body,
|
||||
character_loader,
|
||||
);
|
||||
}
|
||||
},
|
||||
ClientMsg::DeleteCharacter(character_id) => {
|
||||
if let Some(player) = players.get(entity) {
|
||||
character_loader.delete_character(
|
||||
entity,
|
||||
player.uuid().to_string(),
|
||||
character_id,
|
||||
);
|
||||
}
|
||||
},
|
||||
ClientMsg::UnlockSkill(skill) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.unlock_skill(skill));
|
||||
},
|
||||
ClientMsg::RefundSkill(skill) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.refund_skill(skill));
|
||||
},
|
||||
ClientMsg::UnlockSkillGroup(skill_group_type) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.unlock_skill_group(skill_group_type));
|
||||
},
|
||||
if let Some(msg) = m1 {
|
||||
client.network_error |= b1;
|
||||
Self::handle_client_msg(
|
||||
server_emitter,
|
||||
new_chat_msgs,
|
||||
entity,
|
||||
client,
|
||||
player_metrics,
|
||||
uids,
|
||||
chat_modes,
|
||||
msg?,
|
||||
)?;
|
||||
}
|
||||
if let Some(msg) = m2 {
|
||||
client.network_error |= b2;
|
||||
Self::handle_client_in_game_msg(
|
||||
server_emitter,
|
||||
entity,
|
||||
client,
|
||||
terrain,
|
||||
network_metrics,
|
||||
can_build,
|
||||
force_updates,
|
||||
stats,
|
||||
block_changes,
|
||||
positions,
|
||||
velocities,
|
||||
orientations,
|
||||
players,
|
||||
controllers,
|
||||
settings,
|
||||
msg?,
|
||||
)?;
|
||||
}
|
||||
if let Some(msg) = m3 {
|
||||
client.network_error |= b3;
|
||||
Self::handle_client_character_screen_msg(
|
||||
server_emitter,
|
||||
new_chat_msgs,
|
||||
entity,
|
||||
client,
|
||||
character_loader,
|
||||
uids,
|
||||
players,
|
||||
editable_settings,
|
||||
alias_validator,
|
||||
msg?,
|
||||
)?;
|
||||
}
|
||||
if let Some(msg) = m4 {
|
||||
client.network_error |= b4;
|
||||
Self::handle_ping_msg(client, msg?)?;
|
||||
}
|
||||
if let Some(msg) = m5 {
|
||||
client.network_error |= b5;
|
||||
Self::handle_register_msg(
|
||||
player_list,
|
||||
new_players,
|
||||
entity,
|
||||
client,
|
||||
player_metrics,
|
||||
login_provider,
|
||||
admins,
|
||||
players,
|
||||
editable_settings,
|
||||
msg?,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -448,8 +573,8 @@ impl<'a> System<'a> for Sys {
|
||||
WriteStorage<'a, Client>,
|
||||
WriteStorage<'a, Controller>,
|
||||
Read<'a, Settings>,
|
||||
ReadExpect<'a, AliasValidator>,
|
||||
ReadExpect<'a, EditableSettings>,
|
||||
ReadExpect<'a, AliasValidator>,
|
||||
);
|
||||
|
||||
#[allow(clippy::match_ref_pats)] // TODO: Pending review in #587
|
||||
@ -481,8 +606,8 @@ impl<'a> System<'a> for Sys {
|
||||
mut clients,
|
||||
mut controllers,
|
||||
settings,
|
||||
alias_validator,
|
||||
editable_settings,
|
||||
alias_validator,
|
||||
): Self::SystemData,
|
||||
) {
|
||||
span!(_guard, "run", "message::Sys::run");
|
||||
@ -515,7 +640,7 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
let network_err: Result<(), crate::error::Error> = block_on(async {
|
||||
//TIMEOUT 0.02 ms for msg handling
|
||||
let work_future = Self::handle_client_msg(
|
||||
let work_future = Self::handle_messages(
|
||||
&mut server_emitter,
|
||||
&mut new_chat_msgs,
|
||||
&player_list,
|
||||
@ -541,8 +666,8 @@ impl<'a> System<'a> for Sys {
|
||||
&mut players,
|
||||
&mut controllers,
|
||||
&settings,
|
||||
&alias_validator,
|
||||
&editable_settings,
|
||||
&alias_validator,
|
||||
);
|
||||
select!(
|
||||
_ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()),
|
||||
@ -550,8 +675,16 @@ impl<'a> System<'a> for Sys {
|
||||
)
|
||||
});
|
||||
|
||||
// Update client ping.
|
||||
if cnt > 0 {
|
||||
// Network error
|
||||
if network_err.is_err() {
|
||||
debug!(?entity, "postbox error with client, disconnecting");
|
||||
player_metrics
|
||||
.clients_disconnected
|
||||
.with_label_values(&["network_error"])
|
||||
.inc();
|
||||
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
|
||||
} else if cnt > 0 {
|
||||
// Update client ping.
|
||||
client.last_ping = time.0
|
||||
} else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64
|
||||
// Timeout
|
||||
@ -562,18 +695,9 @@ impl<'a> System<'a> for Sys {
|
||||
.with_label_values(&["timeout"])
|
||||
.inc();
|
||||
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
|
||||
} else if network_err.is_err()
|
||||
// Postbox error
|
||||
{
|
||||
debug!(?entity, "postbox error with client, disconnecting");
|
||||
player_metrics
|
||||
.clients_disconnected
|
||||
.with_label_values(&["network_error"])
|
||||
.inc();
|
||||
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
|
||||
} else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 * 0.5 {
|
||||
// Try pinging the client if the timeout is nearing.
|
||||
client.notify(ServerMsg::Ping);
|
||||
client.send_msg(PingMsg::Ping);
|
||||
}
|
||||
}
|
||||
|
||||
@ -581,14 +705,15 @@ impl<'a> System<'a> for Sys {
|
||||
// Tell all clients to add them to the player list.
|
||||
for entity in new_players {
|
||||
if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) {
|
||||
let msg = ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo {
|
||||
player_alias: player.alias.clone(),
|
||||
is_online: true,
|
||||
is_admin: admins.get(entity).is_some(),
|
||||
character: None, // new players will be on character select.
|
||||
}));
|
||||
for client in (&mut clients).join().filter(|c| c.is_registered()) {
|
||||
client.notify(msg.clone())
|
||||
let msg =
|
||||
ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo {
|
||||
player_alias: player.alias.clone(),
|
||||
is_online: true,
|
||||
is_admin: admins.get(entity).is_some(),
|
||||
character: None, // new players will be on character select.
|
||||
}));
|
||||
for client in (&mut clients).join().filter(|c| c.registered) {
|
||||
client.send_msg(msg.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use super::{
|
||||
use crate::client::{self, Client, RegionSubscription};
|
||||
use common::{
|
||||
comp::{Ori, Player, Pos, Vel},
|
||||
msg::ServerMsg,
|
||||
msg::ServerGeneral,
|
||||
region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap},
|
||||
span,
|
||||
sync::Uid,
|
||||
@ -80,7 +80,7 @@ impl<'a> System<'a> for Sys {
|
||||
)
|
||||
.join()
|
||||
.filter_map(|(client, s, pos, player, e)| {
|
||||
if client.is_ingame() {
|
||||
if client.in_game.is_some() {
|
||||
player.view_distance.map(|v| (client, s, pos, v, e))
|
||||
} else {
|
||||
None
|
||||
@ -153,7 +153,7 @@ impl<'a> System<'a> for Sys {
|
||||
.map(|key| subscription.regions.contains(key))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
client.notify(ServerMsg::DeleteEntity(uid));
|
||||
client.send_msg(ServerGeneral::DeleteEntity(uid));
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -161,7 +161,7 @@ impl<'a> System<'a> for Sys {
|
||||
}
|
||||
// Tell client to delete entities in the region
|
||||
for (&uid, _) in (&uids, region.entities()).join() {
|
||||
client.notify(ServerMsg::DeleteEntity(uid));
|
||||
client.send_msg(ServerGeneral::DeleteEntity(uid));
|
||||
}
|
||||
}
|
||||
// Send deleted entities since they won't be processed for this client in entity
|
||||
@ -171,7 +171,7 @@ impl<'a> System<'a> for Sys {
|
||||
.iter()
|
||||
.flat_map(|v| v.iter())
|
||||
{
|
||||
client.notify(ServerMsg::DeleteEntity(Uid(*uid)));
|
||||
client.send_msg(ServerGeneral::DeleteEntity(Uid(*uid)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,7 +196,7 @@ impl<'a> System<'a> for Sys {
|
||||
{
|
||||
// Send message to create entity and tracked components and physics
|
||||
// components
|
||||
client.notify(ServerMsg::CreateEntity(
|
||||
client.send_msg(ServerGeneral::CreateEntity(
|
||||
tracked_comps.create_entity_package(
|
||||
entity,
|
||||
Some(*pos),
|
||||
@ -249,7 +249,7 @@ pub fn initialize_region_subscription(world: &World, entity: specs::Entity) {
|
||||
.join()
|
||||
{
|
||||
// Send message to create entity and tracked components and physics components
|
||||
client.notify(ServerMsg::CreateEntity(
|
||||
client.send_msg(ServerGeneral::CreateEntity(
|
||||
tracked_comps.create_entity_package(
|
||||
entity,
|
||||
Some(*pos),
|
||||
|
@ -4,7 +4,7 @@ use common::{
|
||||
comp::{self, bird_medium, Alignment, Player, Pos},
|
||||
event::{EventBus, ServerEvent},
|
||||
generation::get_npc_name,
|
||||
msg::ServerMsg,
|
||||
msg::ServerGeneral,
|
||||
npc::NPC_NAMES,
|
||||
span,
|
||||
state::TerrainChanges,
|
||||
@ -63,7 +63,7 @@ impl<'a> System<'a> for Sys {
|
||||
Ok((chunk, supplement)) => (chunk, supplement),
|
||||
Err(Some(entity)) => {
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(ServerMsg::TerrainChunkUpdate {
|
||||
client.send_msg(ServerGeneral::TerrainChunkUpdate {
|
||||
key,
|
||||
chunk: Err(()),
|
||||
});
|
||||
@ -90,7 +90,7 @@ impl<'a> System<'a> for Sys {
|
||||
.magnitude_squared();
|
||||
|
||||
if adjusted_dist_sqr <= view_distance.pow(2) {
|
||||
client.notify(ServerMsg::TerrainChunkUpdate {
|
||||
client.send_msg(ServerGeneral::TerrainChunkUpdate {
|
||||
key,
|
||||
chunk: Ok(Box::new(chunk.clone())),
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ use super::SysTimer;
|
||||
use crate::client::Client;
|
||||
use common::{
|
||||
comp::{Player, Pos},
|
||||
msg::ServerMsg,
|
||||
msg::ServerGeneral,
|
||||
span,
|
||||
state::TerrainChanges,
|
||||
terrain::TerrainGrid,
|
||||
@ -38,7 +38,7 @@ impl<'a> System<'a> for Sys {
|
||||
.map(|vd| super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, vd))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
client.notify(ServerMsg::TerrainChunkUpdate {
|
||||
client.send_msg(ServerGeneral::TerrainChunkUpdate {
|
||||
key: *chunk_key,
|
||||
chunk: Ok(Box::new(match terrain.get_key(*chunk_key) {
|
||||
Some(chunk) => chunk.clone(),
|
||||
@ -51,10 +51,10 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// TODO: Don't send all changed blocks to all clients
|
||||
// Sync changed blocks
|
||||
let msg = ServerMsg::TerrainBlockUpdates(terrain_changes.modified_blocks.clone());
|
||||
let msg = ServerGeneral::TerrainBlockUpdates(terrain_changes.modified_blocks.clone());
|
||||
for (player, client) in (&players, &mut clients).join() {
|
||||
if player.view_distance.is_some() {
|
||||
client.notify(msg.clone());
|
||||
client.send_msg(msg.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ use super::SysTimer;
|
||||
use crate::client::Client;
|
||||
use common::{
|
||||
comp::{Player, Pos, Waypoint, WaypointArea},
|
||||
msg::{Notification, ServerMsg},
|
||||
msg::{Notification, ServerGeneral},
|
||||
span,
|
||||
state::Time,
|
||||
};
|
||||
@ -42,7 +42,8 @@ impl<'a> System<'a> for Sys {
|
||||
if let Ok(wp_old) = waypoints.insert(entity, Waypoint::new(player_pos.0, *time))
|
||||
{
|
||||
if wp_old.map_or(true, |w| w.elapsed(*time) > NOTIFY_TIME) {
|
||||
client.notify(ServerMsg::Notification(Notification::WaypointSaved));
|
||||
client
|
||||
.send_msg(ServerGeneral::Notification(Notification::WaypointSaved));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use crate::{
|
||||
Direction, GlobalState, PlayState, PlayStateResult,
|
||||
};
|
||||
use client::{self, Client};
|
||||
use common::{assets::Asset, comp, msg::ClientState, span, state::DeltaTime};
|
||||
use common::{assets::Asset, comp, span, state::DeltaTime};
|
||||
use specs::WorldExt;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use tracing::error;
|
||||
@ -61,8 +61,11 @@ impl PlayState for CharSelectionState {
|
||||
|
||||
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<WinEvent>) -> PlayStateResult {
|
||||
span!(_guard, "tick", "<CharSelectionState as PlayState>::tick");
|
||||
let client_state = self.client.borrow().get_client_state();
|
||||
if let ClientState::Pending | ClientState::Registered = client_state {
|
||||
let (client_in_game, client_registered) = {
|
||||
let client = self.client.borrow();
|
||||
(client.in_game(), client.registered())
|
||||
};
|
||||
if client_in_game.is_none() && client_registered {
|
||||
// Handle window events
|
||||
for event in events {
|
||||
if self.char_selection_ui.handle_event(event.clone()) {
|
||||
|
@ -20,7 +20,6 @@ use common::{
|
||||
MAX_PICKUP_RANGE_SQR,
|
||||
},
|
||||
event::EventBus,
|
||||
msg::ClientState,
|
||||
outcome::Outcome,
|
||||
span,
|
||||
terrain::{Block, BlockKind},
|
||||
@ -212,9 +211,11 @@ impl PlayState for SessionState {
|
||||
));
|
||||
|
||||
// TODO: can this be a method on the session or are there borrowcheck issues?
|
||||
|
||||
let client_state = self.client.borrow().get_client_state();
|
||||
if let ClientState::Pending | ClientState::Character = client_state {
|
||||
let (client_in_game, client_registered) = {
|
||||
let client = self.client.borrow();
|
||||
(client.in_game(), client.registered())
|
||||
};
|
||||
if client_in_game.is_some() {
|
||||
// Update MyEntity
|
||||
// Note: Alternatively, the client could emit an event when the entity changes
|
||||
// which may or may not be more elegant
|
||||
@ -1088,7 +1089,7 @@ impl PlayState for SessionState {
|
||||
self.cleanup();
|
||||
|
||||
PlayStateResult::Continue
|
||||
} else if let ClientState::Registered = client_state {
|
||||
} else if client_registered && client_in_game.is_none() {
|
||||
PlayStateResult::Switch(Box::new(CharSelectionState::new(
|
||||
global_state,
|
||||
Rc::clone(&self.client),
|
||||
|
@ -38,7 +38,7 @@ use crate::{
|
||||
use common::{
|
||||
comp::{self, bird_medium, quadruped_low, quadruped_medium, quadruped_small},
|
||||
generation::{ChunkSupplement, EntityInfo},
|
||||
msg::server::WorldMapMsg,
|
||||
msg::WorldMapMsg,
|
||||
terrain::{Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
|
||||
vol::{ReadVol, RectVolSize, WriteVol},
|
||||
};
|
||||
|
@ -33,7 +33,7 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
assets,
|
||||
msg::server::WorldMapMsg,
|
||||
msg::WorldMapMsg,
|
||||
store::Id,
|
||||
terrain::{
|
||||
map::MapConfig, uniform_idx_as_vec2, vec2_as_uniform_idx, BiomeKind, MapSizeLg,
|
||||
|
Loading…
Reference in New Issue
Block a user