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