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#[expect(clippy::large_enum_variant)]
35#[derive(Debug, Clone)]
36pub enum ServerMsg {
37 Info(ServerInfo),
39 Init(Box<ServerInit>),
41 RegisterAnswer(ServerRegisterAnswer),
43 General(ServerGeneral),
45 Ping(PingMsg),
46}
47
48#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
146pub enum ServerGeneral {
147 CharacterDataLoadResult(Result<UpdateCharacterMetadata, String>),
150 CharacterListUpdate(Vec<CharacterItem>),
152 CharacterActionError(String),
154 CharacterCreated(character::CharacterId),
156 CharacterEdited(character::CharacterId),
157 CharacterSuccess,
158 SpectatorSuccess(Vec3<f32>),
159 GroupUpdate(comp::group::ChangeNotification<Uid>),
161 Invite {
163 inviter: Uid,
164 timeout: Duration,
165 kind: InviteKind,
166 },
167 InvitePending(Uid),
170 GroupInventoryUpdate(comp::FrontendItem, Uid),
172 InviteComplete {
178 target: Uid,
179 answer: InviteAnswer,
180 kind: InviteKind,
181 },
182 ExitInGameSuccess,
185 InventoryUpdate(comp::Inventory, Vec<comp::InventoryUpdateEvent>),
186 Dialogue(Uid, rtsim::Dialogue<true>),
187 SetViewDistance(u32),
192 Outcomes(Vec<Outcome>),
193 Knockback(Vec3<f32>),
194 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 PlayerListUpdate(PlayerListUpdate),
206 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 Notification(Notification),
219 UpdatePendingTrade(TradeId, PendingTrade, Option<SitePrices>),
220 FinishedTrade(TradeResult),
221 SiteEconomy(EconomyInfo),
223 MapMarker(comp::MapMarkerUpdate),
224 WeatherUpdate(SharedWeatherGrid),
225 LocalWindUpdate(Vec2<f32>),
226 SpectatePosition(Vec3<f32>),
229 PluginData(Vec<u8>),
231 UpdateRecipes,
234 SetPlayerRole(Option<AdminRole>),
235 Gizmos(Vec<Gizmos>),
236}
237
238impl ServerGeneral {
239 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#[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
275pub 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 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#[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 pub until: Option<i64>,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub enum DisconnectReason {
320 Shutdown,
322 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 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 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 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}