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