veloren_common_net/msg/
server.rs

1use super::{
2    ClientType, CompressedData, EcsCompPacket, PingMsg, QuadPngEncoding, TriPngEncoding,
3    WidePacking, WireChonk, world_msg::EconomyInfo,
4};
5use crate::sync;
6use common::{
7    calendar::Calendar,
8    character::{self, CharacterItem},
9    comp::{
10        self, AdminRole, Content, body::Gender, invite::InviteKind, item::MaterialStatManifest,
11    },
12    event::{PluginHash, UpdateCharacterMetadata},
13    lod,
14    outcome::Outcome,
15    recipe::{ComponentRecipeBook, RecipeBookManifest, RepairRecipeBook},
16    resources::{BattleMode, Time, TimeOfDay, TimeScale},
17    rtsim,
18    shared_server_config::ServerConstants,
19    terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
20    trade::{PendingTrade, SitePrices, TradeId, TradeResult},
21    uid::Uid,
22    uuid::Uuid,
23    weather::SharedWeatherGrid,
24};
25use hashbrown::HashMap;
26use serde::{Deserialize, Serialize};
27use std::time::Duration;
28use tracing::warn;
29use vek::*;
30
31///This struct contains all messages the server might send (on different
32/// streams though)
33#[derive(Debug, Clone)]
34pub enum ServerMsg {
35    /// Basic info about server, send ONCE, clients need it to Register
36    Info(ServerInfo),
37    /// Initial data package, send BEFORE Register ONCE. Not Register relevant
38    Init(Box<ServerInit>),
39    /// Result to `ClientMsg::Register`. send ONCE
40    RegisterAnswer(ServerRegisterAnswer),
41    /// Msg that can be send ALWAYS as soon as client is registered, e.g. `Chat`
42    General(ServerGeneral),
43    Ping(PingMsg),
44}
45
46/*
472nd Level Enums
48*/
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ServerInfo {
52    pub name: String,
53    pub git_hash: String,
54    pub git_date: String,
55    pub auth_provider: Option<String>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, Default)]
59pub struct ServerDescription {
60    pub motd: String,
61    pub rules: Option<String>,
62}
63
64/// Reponse To ClientType
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub enum ServerInit {
67    GameSync {
68        entity_package: sync::EntityPackage<EcsCompPacket>,
69        role: Option<AdminRole>,
70        time_of_day: TimeOfDay,
71        max_group_size: u32,
72        client_timeout: Duration,
73        world_map: crate::msg::world_msg::WorldMapMsg,
74        recipe_book: RecipeBookManifest,
75        component_recipe_book: ComponentRecipeBook,
76        repair_recipe_book: RepairRecipeBook,
77        material_stats: MaterialStatManifest,
78        ability_map: comp::item::tool::AbilityMap,
79        server_constants: ServerConstants,
80        description: ServerDescription,
81        active_plugins: Vec<PluginHash>,
82    },
83}
84
85pub type ServerRegisterAnswer = Result<(), RegisterError>;
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub enum SerializedTerrainChunk {
89    DeflatedChonk(CompressedData<TerrainChunk>),
90    QuadPng(WireChonk<QuadPngEncoding<4>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>),
91    TriPng(WireChonk<TriPngEncoding<false>, WidePacking<true>, TerrainChunkMeta, TerrainChunkSize>),
92}
93
94impl SerializedTerrainChunk {
95    pub fn approx_len(&self) -> usize {
96        match self {
97            SerializedTerrainChunk::DeflatedChonk(data) => data.data.len(),
98            SerializedTerrainChunk::QuadPng(data) => data.data.data.len(),
99            SerializedTerrainChunk::TriPng(data) => data.data.data.len(),
100        }
101    }
102
103    pub fn via_heuristic(chunk: &TerrainChunk, lossy_compression: bool) -> Self {
104        if lossy_compression && (chunk.get_max_z() - chunk.get_min_z() <= 128) {
105            Self::quadpng(chunk)
106        } else {
107            Self::deflate(chunk)
108        }
109    }
110
111    pub fn deflate(chunk: &TerrainChunk) -> Self {
112        Self::DeflatedChonk(CompressedData::compress(chunk, 1))
113    }
114
115    pub fn quadpng(chunk: &TerrainChunk) -> Self {
116        if let Some(wc) = WireChonk::from_chonk(QuadPngEncoding(), WidePacking(), chunk) {
117            Self::QuadPng(wc)
118        } else {
119            warn!("Image encoding failure occurred, falling back to deflate");
120            Self::deflate(chunk)
121        }
122    }
123
124    pub fn tripng(chunk: &TerrainChunk) -> Self {
125        if let Some(wc) = WireChonk::from_chonk(TriPngEncoding(), WidePacking(), chunk) {
126            Self::TriPng(wc)
127        } else {
128            warn!("Image encoding failure occurred, falling back to deflate");
129            Self::deflate(chunk)
130        }
131    }
132
133    pub fn to_chunk(&self) -> Option<TerrainChunk> {
134        match self {
135            Self::DeflatedChonk(chonk) => chonk.decompress(),
136            Self::QuadPng(wc) => wc.to_chonk(),
137            Self::TriPng(wc) => wc.to_chonk(),
138        }
139    }
140}
141
142/// Messages sent from the server to the client
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub enum ServerGeneral {
145    //Character Screen related
146    /// Result of loading character data
147    CharacterDataLoadResult(Result<UpdateCharacterMetadata, String>),
148    /// A list of characters belonging to the a authenticated player was sent
149    CharacterListUpdate(Vec<CharacterItem>),
150    /// An error occurred while creating or deleting a character
151    CharacterActionError(String),
152    /// A new character was created
153    CharacterCreated(character::CharacterId),
154    CharacterEdited(character::CharacterId),
155    CharacterSuccess,
156    SpectatorSuccess(Vec3<f32>),
157    //Ingame related
158    GroupUpdate(comp::group::ChangeNotification<Uid>),
159    /// Indicate to the client that they are invited to join a group
160    Invite {
161        inviter: Uid,
162        timeout: Duration,
163        kind: InviteKind,
164    },
165    /// Indicate to the client that their sent invite was not invalid and is
166    /// currently pending
167    InvitePending(Uid),
168    /// Update the HUD of the clients in the group
169    GroupInventoryUpdate(comp::FrontendItem, Uid),
170    /// Note: this could potentially include all the failure cases such as
171    /// inviting yourself in which case the `InvitePending` message could be
172    /// removed and the client could consider their invite pending until
173    /// they receive this message Indicate to the client the result of their
174    /// invite
175    InviteComplete {
176        target: Uid,
177        answer: InviteAnswer,
178        kind: InviteKind,
179    },
180    /// Trigger cleanup for when the client goes back to the `Registered` state
181    /// from an ingame state
182    ExitInGameSuccess,
183    InventoryUpdate(comp::Inventory, Vec<comp::InventoryUpdateEvent>),
184    Dialogue(Uid, rtsim::Dialogue<true>),
185    /// NOTE: The client can infer that entity view distance will be at most the
186    /// terrain view distance that we send here (and if lower it won't be
187    /// modified). So we just need to send the terrain VD back to the client
188    /// if corrections are made.
189    SetViewDistance(u32),
190    Outcomes(Vec<Outcome>),
191    Knockback(Vec3<f32>),
192    // Ingame related AND terrain stream
193    TerrainChunkUpdate {
194        key: Vec2<i32>,
195        chunk: Result<SerializedTerrainChunk, ()>,
196    },
197    LodZoneUpdate {
198        key: Vec2<i32>,
199        zone: lod::Zone,
200    },
201    TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>),
202    // Always possible
203    PlayerListUpdate(PlayerListUpdate),
204    /// A message to go into the client chat box. The client is responsible for
205    /// formatting the message and turning it into a speech bubble.
206    ChatMsg(comp::ChatMsg),
207    ChatMode(comp::ChatMode),
208    SetPlayerEntity(Uid),
209    TimeOfDay(TimeOfDay, Calendar, Time, TimeScale),
210    EntitySync(sync::EntitySyncPackage),
211    CompSync(sync::CompSyncPackage<EcsCompPacket>, u64),
212    CreateEntity(sync::EntityPackage<EcsCompPacket>),
213    DeleteEntity(Uid),
214    Disconnect(DisconnectReason),
215    /// Send a popup notification such as "Waypoint Saved"
216    Notification(Notification),
217    UpdatePendingTrade(TradeId, PendingTrade, Option<SitePrices>),
218    FinishedTrade(TradeResult),
219    /// Economic information about sites
220    SiteEconomy(EconomyInfo),
221    MapMarker(comp::MapMarkerUpdate),
222    WeatherUpdate(SharedWeatherGrid),
223    LocalWindUpdate(Vec2<f32>),
224    /// Suggest the client to spectate a position. Called after client has
225    /// requested teleport etc.
226    SpectatePosition(Vec3<f32>),
227    /// Plugin data requested from the server
228    PluginData(Vec<u8>),
229    /// Update the list of available recipes. Usually called after a new recipe
230    /// is acquired
231    UpdateRecipes,
232    SetPlayerRole(Option<AdminRole>),
233}
234
235impl ServerGeneral {
236    // TODO: Don't use `Into<Content>` since this treats all strings as plaintext,
237    // properly localise server messages
238    pub fn server_msg(chat_type: comp::ChatType<String>, content: impl Into<Content>) -> Self {
239        ServerGeneral::ChatMsg(chat_type.into_msg(content.into()))
240    }
241}
242
243/*
244end of 2nd level Enums
245*/
246
247/// Inform the client of updates to the player list.
248///
249/// Note: Before emiting any of these, check if the current
250/// [`Client::client_type`] wants to emit login events.
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub enum PlayerListUpdate {
253    Init(HashMap<Uid, PlayerInfo>),
254    Add(Uid, PlayerInfo),
255    SelectedCharacter(Uid, CharacterInfo),
256    ExitCharacter(Uid),
257    Moderator(Uid, bool),
258    Remove(Uid),
259    Alias(Uid, String),
260    UpdateBattleMode(Uid, BattleMode),
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct PlayerInfo {
265    pub is_moderator: bool,
266    pub is_online: bool,
267    pub player_alias: String,
268    pub character: Option<CharacterInfo>,
269    pub uuid: Uuid,
270}
271
272/// used for localisation, filled by client and used by i18n code
273pub struct ChatTypeContext {
274    pub you: Uid,
275    pub player_info: HashMap<Uid, PlayerInfo>,
276    pub entity_name: HashMap<Uid, Content>,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct CharacterInfo {
281    /// The name of specific character, not to be mistaken for player's alias.
282    ///
283    /// We use Content here as for all names, but any character name provided
284    /// directly from a client will be `Content::Plain`
285    pub name: Content,
286    pub gender: Option<Gender>,
287    pub battle_mode: BattleMode,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub enum InviteAnswer {
292    Accepted,
293    Declined,
294    TimedOut,
295}
296
297/// A message that should be displayed to the player, possibly with data to
298/// update the client.
299///
300/// See [`veloren_client::UserNotification`] for the stripped down version,
301/// which the client sends to the UI after removing (and using) any data that is
302/// not relevant to rendering.
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub enum Notification {
305    WaypointSaved { location_name: String },
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
309pub struct BanInfo {
310    pub reason: String,
311    /// Unix timestamp at which the ban will expire
312    pub until: Option<i64>,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub enum DisconnectReason {
317    /// Server shut down
318    Shutdown,
319    /// Client was kicked
320    Kicked(String),
321    Banned(BanInfo),
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
325pub enum RegisterError {
326    AuthError(String),
327    Banned(BanInfo),
328    Kicked(String),
329    InvalidCharacter,
330    NotOnWhitelist,
331    TooManyPlayers,
332    //TODO: InvalidAlias,
333}
334
335impl ServerMsg {
336    pub fn verify(
337        &self,
338        c_type: ClientType,
339        registered: bool,
340        presence: Option<comp::PresenceKind>,
341    ) -> bool {
342        match self {
343            ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => {
344                !registered && presence.is_none()
345            },
346            ServerMsg::General(g) => {
347                registered
348                    && match g {
349                        //Character Screen related
350                        ServerGeneral::CharacterDataLoadResult(_)
351                        | ServerGeneral::CharacterListUpdate(_)
352                        | ServerGeneral::CharacterActionError(_)
353                        | ServerGeneral::CharacterEdited(_)
354                        | ServerGeneral::CharacterCreated(_) => {
355                            c_type != ClientType::ChatOnly && presence.is_none()
356                        },
357                        ServerGeneral::CharacterSuccess | ServerGeneral::SpectatorSuccess(_) => {
358                            c_type == ClientType::Game && presence.is_none()
359                        },
360                        //Ingame related
361                        ServerGeneral::GroupUpdate(_)
362                        | ServerGeneral::Invite { .. }
363                        | ServerGeneral::InvitePending(_)
364                        | ServerGeneral::InviteComplete { .. }
365                        | ServerGeneral::ExitInGameSuccess
366                        | ServerGeneral::InventoryUpdate(_, _)
367                        | ServerGeneral::GroupInventoryUpdate(_, _)
368                        | ServerGeneral::Dialogue(_, _)
369                        | ServerGeneral::TerrainChunkUpdate { .. }
370                        | ServerGeneral::TerrainBlockUpdates(_)
371                        | ServerGeneral::SetViewDistance(_)
372                        | ServerGeneral::Outcomes(_)
373                        | ServerGeneral::Knockback(_)
374                        | ServerGeneral::UpdatePendingTrade(_, _, _)
375                        | ServerGeneral::FinishedTrade(_)
376                        | ServerGeneral::SiteEconomy(_)
377                        | ServerGeneral::MapMarker(_)
378                        | ServerGeneral::WeatherUpdate(_)
379                        | ServerGeneral::LocalWindUpdate(_)
380                        | ServerGeneral::SpectatePosition(_)
381                        | ServerGeneral::UpdateRecipes => {
382                            c_type == ClientType::Game && presence.is_some()
383                        },
384                        // Always possible
385                        ServerGeneral::PlayerListUpdate(_)
386                        | ServerGeneral::ChatMsg(_)
387                        | ServerGeneral::ChatMode(_)
388                        | ServerGeneral::SetPlayerEntity(_)
389                        | ServerGeneral::TimeOfDay(_, _, _, _)
390                        | ServerGeneral::EntitySync(_)
391                        | ServerGeneral::CompSync(_, _)
392                        | ServerGeneral::CreateEntity(_)
393                        | ServerGeneral::DeleteEntity(_)
394                        | ServerGeneral::Disconnect(_)
395                        | ServerGeneral::Notification(_)
396                        | ServerGeneral::SetPlayerRole(_)
397                        | ServerGeneral::LodZoneUpdate { .. } => true,
398                        ServerGeneral::PluginData(_) => true,
399                    }
400            },
401            ServerMsg::Ping(_) => true,
402        }
403    }
404}
405
406impl From<comp::ChatMsg> for ServerGeneral {
407    fn from(v: comp::ChatMsg) -> Self { ServerGeneral::ChatMsg(v) }
408}
409
410impl From<ServerInfo> for ServerMsg {
411    fn from(o: ServerInfo) -> ServerMsg { ServerMsg::Info(o) }
412}
413
414impl From<ServerInit> for ServerMsg {
415    fn from(o: ServerInit) -> ServerMsg { ServerMsg::Init(Box::new(o)) }
416}
417
418impl From<ServerRegisterAnswer> for ServerMsg {
419    fn from(o: ServerRegisterAnswer) -> ServerMsg { ServerMsg::RegisterAnswer(o) }
420}
421
422impl From<ServerGeneral> for ServerMsg {
423    fn from(o: ServerGeneral) -> ServerMsg { ServerMsg::General(o) }
424}
425
426impl From<PingMsg> for ServerMsg {
427    fn from(o: PingMsg) -> ServerMsg { ServerMsg::Ping(o) }
428}