veloren/common/src/msg/server.rs
2020-08-25 12:21:25 +00:00

273 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::{ClientState, EcsCompPacket};
use crate::{
character::CharacterItem,
comp,
outcome::Outcome,
recipe::RecipeBook,
state, sync,
sync::Uid,
terrain::{Block, TerrainChunk},
};
use authc::AuthClientError;
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use vek::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub name: String,
pub description: String,
pub git_hash: String,
pub git_date: String,
pub auth_provider: Option<String>,
}
/// Inform the client of updates to the player list.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlayerListUpdate {
Init(HashMap<Uid, PlayerInfo>),
Add(Uid, PlayerInfo),
SelectedCharacter(Uid, CharacterInfo),
LevelChange(Uid, u32),
Admin(Uid, bool),
Remove(Uid),
Alias(Uid, String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerInfo {
pub is_admin: bool,
pub is_online: bool,
pub player_alias: String,
pub character: Option<CharacterInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CharacterInfo {
pub name: String,
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,
Declined,
TimedOut,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Notification {
WaypointSaved,
}
/// 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,
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,
Shutdown,
TooManyPlayers,
/// Send a popup notification such as "Waypoint Saved"
Notification(Notification),
SetViewDistance(u32),
Outcomes(Vec<Outcome>),
}
#[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,
AuthError(String),
InvalidCharacter,
NotOnWhitelist,
//TODO: InvalidAlias,
}
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) }
}