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