pub mod messages;
use crate::bitbuf::BitWriter;
use crate::edicts::Edict;
use crate::entities::Entity;
use crate::net::EncodeError;
use crate::net::messages::MAX_MESSAGE_DATA_BYTES;
use crate::server::{InterfaceError, Server};
use sdk_raw::bitbuf::BfWrite;
use sdk_raw::user_messages::RecipientFilter;
use sdk_raw::vcall;
use std::ffi::{CStr, CString, c_int};
use std::ptr::NonNull;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RawUserMessage<'a> {
pub name: &'a CStr,
pub data: &'a BitWriter,
}
impl UserMessage for RawUserMessage<'_> {
fn name(&self) -> &CStr {
self.name
}
fn write(&self, out: &mut BitWriter) -> Result<(), EncodeError> {
out.write_bits(self.data);
Ok(())
}
}
#[doc(alias("IRecipientFilter", "CRecipientFilter"))]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Recipients {
players: Vec<c_int>,
reliable: bool,
}
impl Recipients {
pub const fn new() -> Self {
Self {
players: Vec::new(),
reliable: false,
}
}
#[doc(alias("AddAllPlayers"))]
pub fn all_players(server: Server<'_>) -> Result<Self, InterfaceError> {
let engine = server.valve_engine()?;
let max_clients = server
.player_info_manager()?
.global_vars()
.map_or(0, |globals| globals.max_clients());
let mut recipients = Self::new();
for index in 1..=max_clients {
if let Some(edict) = engine
.edict_of_index(index)
.filter(|edict| edict.entity().is_some())
{
recipients.add(edict);
}
}
Ok(recipients)
}
#[doc(alias("CSingleUserRecipientFilter"))]
pub fn player(client: Edict<'_>) -> Self {
let mut recipients = Self::new();
recipients.add(client);
recipients
}
#[doc(alias("CTeamRecipientFilter"))]
pub fn team(server: Server<'_>, team: c_int) -> Result<Self, InterfaceError> {
let engine = server.valve_engine()?;
let players = server.player_info_manager()?;
let max_clients = players
.global_vars()
.map_or(0, |globals| globals.max_clients());
let mut recipients = Self::new();
for index in 1..=max_clients {
if let Some(edict) = engine.edict_of_index(index)
&& players
.player_info(edict)
.is_some_and(|player| player.team() == team)
{
recipients.add(edict);
}
}
Ok(recipients)
}
#[doc(alias("AddRecipient"))]
pub fn add(&mut self, client: Edict<'_>) {
let index = client.index();
if !self.players.contains(&index) {
self.players.push(index);
}
}
pub(crate) const fn filter(&self) -> RecipientFilter<'_> {
RecipientFilter::new(self.players.as_slice(), self.reliable)
}
pub fn is_empty(&self) -> bool {
self.players.is_empty()
}
#[doc(alias("GetRecipientCount"))]
pub fn len(&self) -> usize {
self.players.len()
}
#[doc(alias("GetRecipientIndex"))]
pub fn players(&self) -> &[c_int] {
&self.players
}
#[doc(alias("MakeReliable"))]
pub fn reliable(mut self) -> Self {
self.reliable = true;
self
}
#[doc(alias("RemoveRecipient"))]
pub fn remove(&mut self, client: Edict<'_>) {
let index = client.index();
self.players.retain(|&player| player != index);
}
}
pub trait UserMessage {
fn name(&self) -> &CStr;
fn write(&self, out: &mut BitWriter) -> Result<(), EncodeError>;
}
#[derive(Debug, thiserror::Error)]
pub enum UserMessageError {
#[error(transparent)]
Interface(#[from] InterfaceError),
#[error(transparent)]
Encode(#[from] EncodeError),
#[error("the game registered no user message named {0:?}")]
Unknown(CString),
#[error("the game registered {name:?} with {expected} bytes, but the payload has {bytes}")]
WrongSize {
name: CString,
expected: usize,
bytes: usize,
},
#[error("the engine did not start the message")]
NotStarted,
#[error("the payload did not fit in the engine's buffer")]
Overflow,
#[error("the entity is not networked, so no client knows it")]
NotNetworked,
}
fn finish(
engine: *mut sys::IVEngineServer,
buffer: *mut sys::bf_write,
data: &BitWriter,
) -> Result<(), UserMessageError> {
let buffer = NonNull::new(buffer).ok_or(UserMessageError::NotStarted)?;
let copied = unsafe { BfWrite::append(BfWrite::from_sys(buffer), data.as_words(), data.len()) };
unsafe { vcall!(engine => IVEngineServer_MessageEnd()) };
match copied {
true => Ok(()),
false => Err(UserMessageError::Overflow),
}
}
#[doc(alias("UserMessageBegin", "MessageEnd"))]
pub fn send(
server: Server<'_>,
recipients: &Recipients,
message: &(impl UserMessage + ?Sized),
) -> Result<(), UserMessageError> {
let name = message.name();
let registered = server
.server_game_dll()?
.user_messages()
.find(|registered| registered.name.as_c_str() == name)
.ok_or_else(|| UserMessageError::Unknown(name.to_owned()))?;
let mut data = BitWriter::new();
message.write(&mut data)?;
let bytes = data.byte_len();
match registered.size {
Some(expected) if bytes != expected => {
return Err(UserMessageError::WrongSize {
name: name.to_owned(),
expected,
bytes,
});
}
_ => EncodeError::check_len("user message data", bytes, MAX_MESSAGE_DATA_BYTES)?,
}
let id = c_int::try_from(registered.index)
.ok()
.filter(|&id| id <= u8::MAX.into())
.ok_or_else(|| UserMessageError::Unknown(name.to_owned()))?;
let engine = server.valve_engine()?;
let filter = recipients.filter();
let buffer =
unsafe { vcall!(engine.as_ptr() => IVEngineServer_UserMessageBegin(filter.as_raw(), id)) };
finish(engine.as_ptr(), buffer, &data)
}
#[doc(alias("EntityMessageBegin"))]
pub fn send_entity_message(
server: Server<'_>,
entity: Entity<'_>,
reliable: bool,
data: &BitWriter,
) -> Result<(), UserMessageError> {
EncodeError::check_len(
"entity message data",
data.byte_len(),
MAX_MESSAGE_DATA_BYTES,
)?;
let edict = entity.edict().ok_or(UserMessageError::NotNetworked)?;
let class = entity
.server_class()
.ok_or(UserMessageError::NotNetworked)?;
let engine = server.valve_engine()?;
let buffer = unsafe {
vcall!(engine.as_ptr() => IVEngineServer_EntityMessageBegin(edict.index(), class.as_ptr(), reliable))
};
finish(engine.as_ptr(), buffer, data)
}