use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
use super::types::{
PeerConnectionInfo, PlayerId, PlayerInfo, RoomId, SpectatorInfo, DEFAULT_REGION_ID,
};
#[derive(
Debug,
Clone,
Serialize,
Deserialize,
PartialEq,
Eq,
Default,
Archive,
RkyvSerialize,
RkyvDeserialize,
)]
#[rkyv(compare(PartialEq))]
#[serde(rename_all = "snake_case")]
pub enum LobbyState {
#[default]
Waiting,
Lobby,
Finalized,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Room {
pub id: RoomId,
pub code: String,
pub game_name: String,
pub max_players: u8,
pub supports_authority: bool,
pub players: HashMap<PlayerId, PlayerInfo>,
pub authority_player: Option<PlayerId>,
pub lobby_state: LobbyState,
pub ready_players: Vec<PlayerId>,
pub lobby_started_at: Option<chrono::DateTime<chrono::Utc>>,
pub game_finalized_at: Option<chrono::DateTime<chrono::Utc>>,
pub relay_type: String,
pub region_id: String,
pub application_id: Option<Uuid>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub last_activity: chrono::DateTime<chrono::Utc>,
pub spectators: HashMap<PlayerId, SpectatorInfo>,
pub max_spectators: Option<u8>,
}
impl Room {
#[allow(dead_code)]
pub fn new(
game_name: String,
room_code: String,
max_players: u8,
supports_authority: bool,
relay_type: String,
) -> Self {
let now = chrono::Utc::now();
Self {
id: Uuid::new_v4(),
code: room_code,
game_name,
max_players,
supports_authority,
players: HashMap::new(),
authority_player: None,
lobby_state: LobbyState::Waiting,
ready_players: Vec::new(),
lobby_started_at: None,
game_finalized_at: None,
relay_type,
region_id: DEFAULT_REGION_ID.to_string(),
application_id: None,
created_at: now,
last_activity: now,
spectators: HashMap::new(),
max_spectators: None, }
}
#[allow(dead_code)]
pub fn update_activity(&mut self) {
self.last_activity = chrono::Utc::now();
}
#[allow(dead_code)]
pub fn is_expired(
&self,
empty_timeout: chrono::Duration,
inactive_timeout: chrono::Duration,
) -> bool {
let now = chrono::Utc::now();
if self.players.is_empty() {
now.signed_duration_since(self.created_at) > empty_timeout
} else {
now.signed_duration_since(self.last_activity) > inactive_timeout
}
}
#[allow(dead_code)]
pub fn can_join(&self) -> bool {
self.players.len() < self.max_players as usize
}
#[allow(dead_code)]
pub fn add_player(&mut self, player: PlayerInfo) -> bool {
if self.can_join() {
self.players.insert(player.id, player);
true
} else {
false
}
}
#[allow(dead_code)]
pub fn remove_player(&mut self, player_id: &PlayerId) -> Option<PlayerInfo> {
let removed = self.players.remove(player_id);
if self.authority_player == Some(*player_id) {
self.authority_player = None;
}
removed
}
#[allow(dead_code)]
pub fn set_authority(&mut self, player_id: Option<PlayerId>) -> bool {
if !self.supports_authority {
return false;
}
match player_id {
Some(id) if self.players.contains_key(&id) => {
if let Some(prev_auth) = self.authority_player {
if let Some(player) = self.players.get_mut(&prev_auth) {
player.is_authority = false;
}
}
self.authority_player = Some(id);
if let Some(player) = self.players.get_mut(&id) {
player.is_authority = true;
}
true
}
None => {
if let Some(prev_auth) = self.authority_player.take() {
if let Some(player) = self.players.get_mut(&prev_auth) {
player.is_authority = false;
}
}
true
}
Some(_) => false, }
}
#[allow(dead_code)]
pub fn clear_authority(&mut self) -> bool {
self.set_authority(None)
}
#[allow(dead_code)]
pub fn should_enter_lobby(&self) -> bool {
self.lobby_state == LobbyState::Waiting
&& self.players.len() == self.max_players as usize
&& self.max_players > 1
}
#[allow(dead_code)]
pub fn enter_lobby(&mut self) -> bool {
if self.should_enter_lobby() {
self.lobby_state = LobbyState::Lobby;
self.lobby_started_at = Some(chrono::Utc::now());
self.ready_players.clear();
true
} else {
false
}
}
#[allow(dead_code)]
pub fn set_player_ready(&mut self, player_id: &PlayerId, ready: bool) -> bool {
if self.lobby_state != LobbyState::Lobby || !self.players.contains_key(player_id) {
return false;
}
if ready && !self.ready_players.contains(player_id) {
self.ready_players.push(*player_id);
} else if !ready {
self.ready_players.retain(|id| id != player_id);
}
if let Some(player) = self.players.get_mut(player_id) {
player.is_ready = ready;
}
true
}
#[allow(dead_code)]
pub fn all_players_ready(&self) -> bool {
if self.lobby_state != LobbyState::Lobby {
return false;
}
self.ready_players.len() == self.players.len() && !self.players.is_empty()
}
#[allow(dead_code)]
pub fn finalize_game(&mut self) -> bool {
if self.lobby_state == LobbyState::Lobby && self.all_players_ready() {
self.lobby_state = LobbyState::Finalized;
self.game_finalized_at = Some(chrono::Utc::now());
true
} else {
false
}
}
#[allow(dead_code)]
pub fn get_peer_connections(&self) -> Vec<PeerConnectionInfo> {
self.players
.values()
.map(|player| PeerConnectionInfo {
player_id: player.id,
player_name: player.name.clone(),
is_authority: player.is_authority,
relay_type: self.relay_type.clone(),
connection_info: player.connection_info.clone(),
})
.collect()
}
#[allow(dead_code)]
pub fn is_finalized(&self) -> bool {
self.lobby_state == LobbyState::Finalized
}
#[allow(dead_code)]
pub fn can_spectate(&self) -> bool {
if let Some(max_spectators) = self.max_spectators {
self.spectators.len() < max_spectators as usize
} else {
true }
}
#[allow(dead_code)]
pub fn add_spectator(&mut self, spectator: SpectatorInfo) -> bool {
if self.can_spectate() {
self.spectators.insert(spectator.id, spectator);
true
} else {
false
}
}
#[allow(dead_code)]
pub fn remove_spectator(&mut self, spectator_id: &PlayerId) -> Option<SpectatorInfo> {
self.spectators.remove(spectator_id)
}
#[allow(dead_code)]
pub fn get_spectators(&self) -> Vec<SpectatorInfo> {
self.spectators.values().cloned().collect()
}
}