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::{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}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct PlayerInfo {
264    pub is_moderator: bool,
265    pub is_online: bool,
266    pub player_alias: String,
267    pub character: Option<CharacterInfo>,
268    pub uuid: Uuid,
269}
270
271/// used for localisation, filled by client and used by i18n code
272pub struct ChatTypeContext {
273    pub you: Uid,
274    pub player_info: HashMap<Uid, PlayerInfo>,
275    pub entity_name: HashMap<Uid, String>,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct CharacterInfo {
280    pub name: String,
281    pub gender: Option<Gender>,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub enum InviteAnswer {
286    Accepted,
287    Declined,
288    TimedOut,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
292pub enum Notification {
293    WaypointSaved,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
297pub struct BanInfo {
298    pub reason: String,
299    /// Unix timestamp at which the ban will expire
300    pub until: Option<i64>,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub enum DisconnectReason {
305    /// Server shut down
306    Shutdown,
307    /// Client was kicked
308    Kicked(String),
309    Banned(BanInfo),
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
313pub enum RegisterError {
314    AuthError(String),
315    Banned(BanInfo),
316    Kicked(String),
317    InvalidCharacter,
318    NotOnWhitelist,
319    TooManyPlayers,
320    //TODO: InvalidAlias,
321}
322
323impl ServerMsg {
324    pub fn verify(
325        &self,
326        c_type: ClientType,
327        registered: bool,
328        presence: Option<comp::PresenceKind>,
329    ) -> bool {
330        match self {
331            ServerMsg::Info(_) | ServerMsg::Init(_) | ServerMsg::RegisterAnswer(_) => {
332                !registered && presence.is_none()
333            },
334            ServerMsg::General(g) => {
335                registered
336                    && match g {
337                        //Character Screen related
338                        ServerGeneral::CharacterDataLoadResult(_)
339                        | ServerGeneral::CharacterListUpdate(_)
340                        | ServerGeneral::CharacterActionError(_)
341                        | ServerGeneral::CharacterEdited(_)
342                        | ServerGeneral::CharacterCreated(_) => {
343                            c_type != ClientType::ChatOnly && presence.is_none()
344                        },
345                        ServerGeneral::CharacterSuccess | ServerGeneral::SpectatorSuccess(_) => {
346                            c_type == ClientType::Game && presence.is_none()
347                        },
348                        //Ingame related
349                        ServerGeneral::GroupUpdate(_)
350                        | ServerGeneral::Invite { .. }
351                        | ServerGeneral::InvitePending(_)
352                        | ServerGeneral::InviteComplete { .. }
353                        | ServerGeneral::ExitInGameSuccess
354                        | ServerGeneral::InventoryUpdate(_, _)
355                        | ServerGeneral::GroupInventoryUpdate(_, _)
356                        | ServerGeneral::Dialogue(_, _)
357                        | ServerGeneral::TerrainChunkUpdate { .. }
358                        | ServerGeneral::TerrainBlockUpdates(_)
359                        | ServerGeneral::SetViewDistance(_)
360                        | ServerGeneral::Outcomes(_)
361                        | ServerGeneral::Knockback(_)
362                        | ServerGeneral::UpdatePendingTrade(_, _, _)
363                        | ServerGeneral::FinishedTrade(_)
364                        | ServerGeneral::SiteEconomy(_)
365                        | ServerGeneral::MapMarker(_)
366                        | ServerGeneral::WeatherUpdate(_)
367                        | ServerGeneral::LocalWindUpdate(_)
368                        | ServerGeneral::SpectatePosition(_)
369                        | ServerGeneral::UpdateRecipes => {
370                            c_type == ClientType::Game && presence.is_some()
371                        },
372                        // Always possible
373                        ServerGeneral::PlayerListUpdate(_)
374                        | ServerGeneral::ChatMsg(_)
375                        | ServerGeneral::ChatMode(_)
376                        | ServerGeneral::SetPlayerEntity(_)
377                        | ServerGeneral::TimeOfDay(_, _, _, _)
378                        | ServerGeneral::EntitySync(_)
379                        | ServerGeneral::CompSync(_, _)
380                        | ServerGeneral::CreateEntity(_)
381                        | ServerGeneral::DeleteEntity(_)
382                        | ServerGeneral::Disconnect(_)
383                        | ServerGeneral::Notification(_)
384                        | ServerGeneral::SetPlayerRole(_)
385                        | ServerGeneral::LodZoneUpdate { .. } => true,
386                        ServerGeneral::PluginData(_) => true,
387                    }
388            },
389            ServerMsg::Ping(_) => true,
390        }
391    }
392}
393
394impl From<comp::ChatMsg> for ServerGeneral {
395    fn from(v: comp::ChatMsg) -> Self { ServerGeneral::ChatMsg(v) }
396}
397
398impl From<ServerInfo> for ServerMsg {
399    fn from(o: ServerInfo) -> ServerMsg { ServerMsg::Info(o) }
400}
401
402impl From<ServerInit> for ServerMsg {
403    fn from(o: ServerInit) -> ServerMsg { ServerMsg::Init(Box::new(o)) }
404}
405
406impl From<ServerRegisterAnswer> for ServerMsg {
407    fn from(o: ServerRegisterAnswer) -> ServerMsg { ServerMsg::RegisterAnswer(o) }
408}
409
410impl From<ServerGeneral> for ServerMsg {
411    fn from(o: ServerGeneral) -> ServerMsg { ServerMsg::General(o) }
412}
413
414impl From<PingMsg> for ServerMsg {
415    fn from(o: PingMsg) -> ServerMsg { ServerMsg::Ping(o) }
416}