#[cfg(test)]
#[path = "../tests/players/in_game.rs"]
mod tests;
use crate::edicts::Edict;
use crate::entities::Entity;
use crate::interfaces::{GameClient, ValveEngine};
use crate::players::UserId;
use crate::{InterfaceError, Server};
use std::ffi::{CStr, CString};
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum KickError {
#[error("the kick's reason holds a character that would end its command")]
InvalidReason,
#[error("no player has the client's slot")]
NoPlayer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Player<'s> {
pub client: GameClient<'s>,
pub edict: Edict<'s>,
pub entity: Entity<'s>,
}
impl<'s> Player<'s> {
pub fn is_human(self) -> bool {
!self.client.is_fake()
}
pub fn user_id(self) -> Option<UserId> {
self.client.user_id()
}
}
impl<'s> GameClient<'s> {
#[doc(alias("kickid", "KickClient"))]
pub fn kick(self, engine: ValveEngine<'_>, reason: &CStr) -> Result<(), KickError> {
let reason = reason.to_bytes();
if reason
.iter()
.any(|&byte| byte.is_ascii_control() || matches!(byte, b'"' | b';'))
{
return Err(KickError::InvalidReason);
}
let user_id = self.user_id().ok_or(KickError::NoPlayer)?;
let mut command = format!("kickid {}", user_id.to_raw()).into_bytes();
if !reason.is_empty() {
command.push(b' ');
command.extend_from_slice(reason);
}
command.push(b'\n');
engine.server_command(&CString::new(command).expect("the reason has no NUL"));
Ok(())
}
}
impl<'s> Server<'s> {
pub fn players(&self) -> Result<impl Iterator<Item = Player<'s>> + use<'s>, InterfaceError> {
let engine = self.valve_engine()?;
let players = engine
.game_server()
.into_iter()
.flat_map(|server| server.clients())
.filter(|client| client.is_connected())
.filter_map(move |client| {
let edict = engine.edict_of_index(client.entity_index())?;
let entity = edict.entity()?;
Some(Player {
client,
edict,
entity,
})
});
Ok(players)
}
}