serverstat 0.1.4

Get server info from QuakeWorld servers.
Documentation
use crate::client::QuakeClient;
use crate::qtv::QtvStream;
use crate::server::QuakeServer;
use crate::team;
use crate::team::Team;
pub use quake_serverinfo::Settings;
use quake_text::unicode;

#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct GameServer {
    pub settings: Settings,
    pub teams: Vec<Team>,
    pub players: Vec<Player>,
    pub spectators: Vec<Spectator>,
    pub qtv_stream: Option<QtvStream>,
}

impl From<&QuakeServer> for GameServer {
    fn from(server: &QuakeServer) -> Self {
        let mut clients = server.clients.clone();
        clients.sort();

        let is_teamplay = server.settings.teamplay.is_some_and(|tp| tp > 0);

        let mut players: Vec<Player> = clients
            .iter()
            .filter(|c| !c.is_spectator)
            .map(Player::from)
            .collect();

        if is_teamplay {
            players.sort_by(|a, b| unicode::ord(&a.team, &b.team));
        }

        let spectators: Vec<Spectator> = clients
            .iter()
            .filter(|c| c.is_spectator)
            .map(Spectator::from)
            .collect();

        let teams = match is_teamplay {
            true => team::from_players(&players),
            _ => vec![],
        };

        Self {
            settings: server.settings.clone(),
            teams,
            players,
            spectators,
            qtv_stream: server.qtv_stream.clone(),
        }
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct Player {
    pub id: u32,
    pub name: String,
    pub team: String,
    pub frags: i32,
    pub ping: u32,
    pub time: u32,
    pub top_color: u8,
    pub bottom_color: u8,
    pub skin: String,
    pub auth_cc: String,
    pub is_bot: bool,
}

impl From<&QuakeClient> for Player {
    fn from(client: &QuakeClient) -> Self {
        Self {
            id: client.id,
            name: client.name.clone(),
            team: client.team.clone(),
            frags: client.frags,
            ping: client.ping,
            time: client.time,
            top_color: client.top_color,
            bottom_color: client.bottom_color,
            skin: client.skin.clone(),
            is_bot: client.is_bot,
            auth_cc: client.auth_cc.clone(),
        }
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct Spectator {
    pub id: u32,
    pub name: String,
    pub auth_cc: String,
    pub is_bot: bool,
}

impl From<&QuakeClient> for Spectator {
    fn from(client: &QuakeClient) -> Self {
        Self {
            id: client.id,
            name: client.name.clone(),
            is_bot: client.is_bot,
            auth_cc: client.auth_cc.clone(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use anyhow::Result;
    use std::time::Duration;

    #[tokio::test]
    async fn test_from_gameserver() -> Result<()> {
        let server =
            QuakeServer::try_from_address("quake.se:28501", Duration::from_secs_f32(0.5)).await?;
        assert!(
            GameServer::from(&server)
                .settings
                .hostname
                .unwrap()
                .starts_with("QUAKE.SE KTX:28501"),
        );
        Ok(())
    }

    #[test]
    fn test_player_from_quakeclient() {
        assert_eq!(
            Player::from(&QuakeClient {
                id: 7,
                name: "XantoM".to_string(),
                team: "f0m".to_string(),
                frags: 12,
                ping: 25,
                time: 15,
                top_color: 4,
                bottom_color: 2,
                skin: "XantoM".to_string(),
                auth_cc: "xtm".to_string(),
                is_spectator: false,
                is_bot: false,
            }),
            Player {
                id: 7,
                name: "XantoM".to_string(),
                team: "f0m".to_string(),
                frags: 12,
                ping: 25,
                time: 15,
                top_color: 4,
                bottom_color: 2,
                skin: "XantoM".to_string(),
                auth_cc: "xtm".to_string(),
                is_bot: false,
            }
        );
    }

    #[test]
    fn test_specator_from_quakeclient() {
        assert_eq!(
            Spectator::from(&QuakeClient {
                id: 7,
                name: "XantoM".to_string(),
                team: "f0m".to_string(),
                frags: 12,
                ping: 25,
                time: 15,
                top_color: 4,
                bottom_color: 2,
                skin: "XantoM".to_string(),
                auth_cc: "xtm".to_string(),
                is_spectator: false,
                is_bot: false,
            }),
            Spectator {
                id: 7,
                name: "XantoM".to_string(),
                auth_cc: "xtm".to_string(),
                is_bot: false,
            }
        );
    }
}