use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[allow(dead_code)]
pub const DEFAULT_MAX_GAME_NAME_LENGTH: usize = 64;
#[allow(dead_code)]
pub const DEFAULT_ROOM_CODE_LENGTH: usize = 6;
#[allow(dead_code)]
pub const DEFAULT_MAX_PLAYER_NAME_LENGTH: usize = 32;
#[allow(dead_code)]
pub const DEFAULT_MAX_PLAYERS_LIMIT: u8 = 100;
pub const DEFAULT_REGION_ID: &str = "default";
pub type PlayerId = Uuid;
pub type RoomId = Uuid;
#[derive(
Debug,
Clone,
Copy,
Serialize,
Deserialize,
PartialEq,
Eq,
Default,
Archive,
RkyvSerialize,
RkyvDeserialize,
)]
#[rkyv(compare(PartialEq))]
#[serde(rename_all = "lowercase")]
pub enum RelayTransport {
Tcp,
Udp,
Websocket,
#[default]
Auto,
}
#[derive(
Debug,
Clone,
Copy,
Serialize,
Deserialize,
PartialEq,
Eq,
Default,
Archive,
RkyvSerialize,
RkyvDeserialize,
)]
#[rkyv(compare(PartialEq))]
#[serde(rename_all = "snake_case")]
pub enum GameDataEncoding {
#[default]
Json,
#[serde(rename = "message_pack")]
MessagePack,
#[serde(rename = "rkyv")]
Rkyv,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ConnectionInfo {
#[serde(rename = "direct")]
Direct { host: String, port: u16 },
#[serde(rename = "unity_relay")]
UnityRelay {
allocation_id: String,
connection_data: String,
key: String,
},
#[serde(rename = "relay")]
Relay {
host: String,
port: u16,
#[serde(default)]
transport: RelayTransport,
allocation_id: String,
token: String,
#[serde(skip_serializing_if = "Option::is_none")]
client_id: Option<u16>,
},
#[serde(rename = "webrtc")]
WebRTC {
sdp: Option<String>,
ice_candidates: Vec<String>,
},
#[serde(rename = "custom")]
Custom { data: serde_json::Value },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerInfo {
pub id: PlayerId,
pub name: String,
pub is_authority: bool,
pub is_ready: bool,
pub connected_at: chrono::DateTime<chrono::Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub connection_info: Option<ConnectionInfo>,
#[serde(skip_serializing, skip_deserializing, default)]
pub region_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpectatorInfo {
pub id: PlayerId,
pub name: String,
pub connected_at: chrono::DateTime<chrono::Utc>,
}
#[derive(
Debug,
Clone,
Serialize,
Deserialize,
PartialEq,
Eq,
Default,
Archive,
RkyvSerialize,
RkyvDeserialize,
)]
#[rkyv(compare(PartialEq))]
#[serde(rename_all = "snake_case")]
pub enum SpectatorStateChangeReason {
#[default]
Joined,
VoluntaryLeave,
Disconnected,
Removed,
RoomClosed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerConnectionInfo {
pub player_id: PlayerId,
pub player_name: String,
pub is_authority: bool,
pub relay_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub connection_info: Option<ConnectionInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Archive, RkyvSerialize, RkyvDeserialize)]
pub struct RateLimitInfo {
pub per_minute: u32,
pub per_hour: u32,
pub per_day: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProtocolInfoPayload {
#[serde(skip_serializing_if = "Option::is_none")]
pub platform: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sdk_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub recommended_version: Option<String>,
#[serde(default)]
pub capabilities: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(default)]
pub game_data_formats: Vec<GameDataEncoding>,
#[serde(skip_serializing_if = "Option::is_none")]
pub player_name_rules: Option<PlayerNameRulesPayload>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerNameRulesPayload {
pub max_length: usize,
pub min_length: usize,
pub allow_unicode_alphanumeric: bool,
pub allow_spaces: bool,
pub allow_leading_trailing_whitespace: bool,
#[serde(default)]
pub allowed_symbols: Vec<char>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_allowed_characters: Option<String>,
}
impl PlayerNameRulesPayload {
pub fn from_protocol_config(config: &crate::config::ProtocolConfig) -> Self {
let rules = &config.player_name_validation;
Self {
max_length: config.max_player_name_length,
min_length: 1,
allow_unicode_alphanumeric: rules.allow_unicode_alphanumeric,
allow_spaces: rules.allow_spaces,
allow_leading_trailing_whitespace: rules.allow_leading_trailing_whitespace,
allowed_symbols: rules.allowed_symbols.clone(),
additional_allowed_characters: rules.additional_allowed_characters.clone(),
}
}
}