1use serde::{Deserialize, Serialize};
11use uuid::Uuid;
12
13use crate::error_codes::ErrorCode;
14
15pub type PlayerId = Uuid;
19
20pub type RoomId = Uuid;
22
23#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
27#[serde(rename_all = "lowercase")]
28pub enum RelayTransport {
29 Tcp,
32 Udp,
35 Websocket,
38 #[default]
41 Auto,
42}
43
44#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
46#[serde(rename_all = "snake_case")]
47pub enum GameDataEncoding {
48 #[default]
50 Json,
51 #[serde(rename = "message_pack")]
53 MessagePack,
54 #[serde(rename = "rkyv")]
57 Rkyv,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(tag = "type")]
63pub enum ConnectionInfo {
64 #[serde(rename = "direct")]
66 Direct { host: String, port: u16 },
67 #[serde(rename = "unity_relay")]
69 UnityRelay {
70 allocation_id: String,
71 connection_data: String,
72 key: String,
73 },
74 #[serde(rename = "relay")]
76 Relay {
77 host: String,
79 port: u16,
81 #[serde(default)]
83 transport: RelayTransport,
84 allocation_id: String,
86 token: String,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 client_id: Option<u16>,
91 },
92 #[serde(rename = "webrtc")]
94 WebRTC {
95 sdp: Option<String>,
96 ice_candidates: Vec<String>,
97 },
98 #[serde(rename = "custom")]
100 Custom { data: serde_json::Value },
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
105#[serde(rename_all = "snake_case")]
106pub enum SpectatorStateChangeReason {
107 #[default]
108 Joined,
109 VoluntaryLeave,
110 Disconnected,
111 Removed,
112 RoomClosed,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
117#[serde(rename_all = "snake_case")]
118pub enum LobbyState {
119 #[default]
120 Waiting,
121 Lobby,
122 Finalized,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct PlayerInfo {
130 pub id: PlayerId,
131 pub name: String,
132 pub is_authority: bool,
133 pub is_ready: bool,
134 pub connected_at: String,
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub connection_info: Option<ConnectionInfo>,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct SpectatorInfo {
143 pub id: PlayerId,
144 pub name: String,
145 pub connected_at: String,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct PeerConnectionInfo {
151 pub player_id: PlayerId,
152 pub player_name: String,
153 pub is_authority: bool,
154 pub relay_type: String,
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub connection_info: Option<ConnectionInfo>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct RateLimitInfo {
163 pub per_minute: u32,
165 pub per_hour: u32,
167 pub per_day: u32,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct ProtocolInfoPayload {
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub platform: Option<String>,
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub sdk_version: Option<String>,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub minimum_version: Option<String>,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 pub recommended_version: Option<String>,
182 #[serde(default)]
183 pub capabilities: Vec<String>,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub notes: Option<String>,
186 #[serde(default)]
187 pub game_data_formats: Vec<GameDataEncoding>,
188 #[serde(skip_serializing_if = "Option::is_none")]
189 pub player_name_rules: Option<PlayerNameRulesPayload>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct PlayerNameRulesPayload {
195 pub max_length: usize,
196 pub min_length: usize,
197 pub allow_unicode_alphanumeric: bool,
198 pub allow_spaces: bool,
199 pub allow_leading_trailing_whitespace: bool,
200 #[serde(default)]
201 pub allowed_symbols: Vec<char>,
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub additional_allowed_characters: Option<String>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct RoomJoinedPayload {
212 pub room_id: RoomId,
213 pub room_code: String,
214 pub player_id: PlayerId,
215 pub game_name: String,
216 pub max_players: u8,
217 pub supports_authority: bool,
218 pub current_players: Vec<PlayerInfo>,
219 pub is_authority: bool,
220 pub lobby_state: LobbyState,
221 pub ready_players: Vec<PlayerId>,
222 pub relay_type: String,
223 #[serde(default)]
225 pub current_spectators: Vec<SpectatorInfo>,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct ReconnectedPayload {
232 pub room_id: RoomId,
233 pub room_code: String,
234 pub player_id: PlayerId,
235 pub game_name: String,
236 pub max_players: u8,
237 pub supports_authority: bool,
238 pub current_players: Vec<PlayerInfo>,
239 pub is_authority: bool,
240 pub lobby_state: LobbyState,
241 pub ready_players: Vec<PlayerId>,
242 pub relay_type: String,
243 #[serde(default)]
245 pub current_spectators: Vec<SpectatorInfo>,
246 pub missed_events: Vec<ServerMessage>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct SpectatorJoinedPayload {
254 pub room_id: RoomId,
255 pub room_code: String,
256 pub spectator_id: PlayerId,
257 pub game_name: String,
258 pub current_players: Vec<PlayerInfo>,
259 pub current_spectators: Vec<SpectatorInfo>,
260 pub lobby_state: LobbyState,
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub reason: Option<SpectatorStateChangeReason>,
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
269#[serde(tag = "type", content = "data")]
270pub enum ClientMessage {
271 Authenticate {
274 app_id: String,
276 #[serde(skip_serializing_if = "Option::is_none")]
278 sdk_version: Option<String>,
279 #[serde(skip_serializing_if = "Option::is_none")]
281 platform: Option<String>,
282 #[serde(skip_serializing_if = "Option::is_none")]
284 game_data_format: Option<GameDataEncoding>,
285 },
286 JoinRoom {
288 game_name: String,
289 room_code: Option<String>,
290 player_name: String,
291 max_players: Option<u8>,
292 supports_authority: Option<bool>,
293 #[serde(default)]
296 relay_transport: Option<RelayTransport>,
297 },
298 LeaveRoom,
300 GameData { data: serde_json::Value },
302 AuthorityRequest { become_authority: bool },
304 PlayerReady,
306 ProvideConnectionInfo { connection_info: ConnectionInfo },
308 Ping,
310 Reconnect {
312 player_id: PlayerId,
313 room_id: RoomId,
314 auth_token: String,
316 },
317 JoinAsSpectator {
319 game_name: String,
320 room_code: String,
321 spectator_name: String,
322 },
323 LeaveSpectator,
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
329#[serde(tag = "type", content = "data")]
330pub enum ServerMessage {
331 Authenticated {
333 app_name: String,
335 #[serde(skip_serializing_if = "Option::is_none")]
337 organization: Option<String>,
338 rate_limits: RateLimitInfo,
340 },
341 ProtocolInfo(ProtocolInfoPayload),
343 AuthenticationError {
345 error: String,
347 error_code: ErrorCode,
349 },
350 RoomJoined(Box<RoomJoinedPayload>),
352 RoomJoinFailed {
354 reason: String,
355 #[serde(skip_serializing_if = "Option::is_none")]
356 error_code: Option<ErrorCode>,
357 },
358 RoomLeft,
360 PlayerJoined { player: PlayerInfo },
362 PlayerLeft { player_id: PlayerId },
364 GameData {
366 from_player: PlayerId,
367 data: serde_json::Value,
368 },
369 GameDataBinary {
372 from_player: PlayerId,
373 encoding: GameDataEncoding,
374 #[serde(with = "serde_bytes")]
375 payload: Vec<u8>,
376 },
377 AuthorityChanged {
379 authority_player: Option<PlayerId>,
380 you_are_authority: bool,
381 },
382 AuthorityResponse {
384 granted: bool,
385 reason: Option<String>,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 error_code: Option<ErrorCode>,
388 },
389 LobbyStateChanged {
391 lobby_state: LobbyState,
392 ready_players: Vec<PlayerId>,
393 all_ready: bool,
394 },
395 GameStarting {
397 peer_connections: Vec<PeerConnectionInfo>,
398 },
399 Pong,
401 Reconnected(Box<ReconnectedPayload>),
403 ReconnectionFailed {
405 reason: String,
406 error_code: ErrorCode,
407 },
408 PlayerReconnected { player_id: PlayerId },
410 SpectatorJoined(Box<SpectatorJoinedPayload>),
412 SpectatorJoinFailed {
414 reason: String,
415 #[serde(skip_serializing_if = "Option::is_none")]
416 error_code: Option<ErrorCode>,
417 },
418 SpectatorLeft {
420 #[serde(skip_serializing_if = "Option::is_none")]
421 room_id: Option<RoomId>,
422 #[serde(skip_serializing_if = "Option::is_none")]
423 room_code: Option<String>,
424 #[serde(skip_serializing_if = "Option::is_none")]
425 reason: Option<SpectatorStateChangeReason>,
426 #[serde(default)]
427 current_spectators: Vec<SpectatorInfo>,
428 },
429 NewSpectatorJoined {
431 spectator: SpectatorInfo,
432 #[serde(default)]
433 current_spectators: Vec<SpectatorInfo>,
434 #[serde(skip_serializing_if = "Option::is_none")]
435 reason: Option<SpectatorStateChangeReason>,
436 },
437 SpectatorDisconnected {
439 spectator_id: PlayerId,
440 #[serde(skip_serializing_if = "Option::is_none")]
441 reason: Option<SpectatorStateChangeReason>,
442 #[serde(default)]
443 current_spectators: Vec<SpectatorInfo>,
444 },
445 Error {
447 message: String,
448 #[serde(skip_serializing_if = "Option::is_none")]
449 error_code: Option<ErrorCode>,
450 },
451}