pub mod messages;
use crate::abi::CppDestructors;
use crate::bitbuf::{BitWriter, RawBfWrite};
use crate::edicts::Edict;
use crate::entities::Entity;
use crate::ffi::vcall;
use crate::net::EncodeError;
use crate::net::messages::MAX_MESSAGE_DATA_BYTES;
use crate::server::{InterfaceError, Server};
use std::ffi::{CStr, c_int};
use std::mem::offset_of;
use std::ptr::NonNull;
pub trait UserMessage {
fn name(&self) -> &CStr;
fn write(&self, out: &mut BitWriter) -> Result<(), EncodeError>;
}
#[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")]
#[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,
}
}
pub fn player(client: Edict<'_>) -> Self {
let mut recipients = Self::new();
recipients.add(client);
recipients
}
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)
}
pub fn reliable(mut self) -> Self {
self.reliable = true;
self
}
pub fn add(&mut self, client: Edict<'_>) {
let index = client.index();
if !self.players.contains(&index) {
self.players.push(index);
}
}
pub fn len(&self) -> usize {
self.players.len()
}
pub fn is_empty(&self) -> bool {
self.players.is_empty()
}
}
#[repr(C)]
struct RecipientFilter<'a> {
vtable: &'static RecipientFilterVtable,
recipients: &'a Recipients,
}
#[repr(C)]
struct RecipientFilterVtable {
destructor: CppDestructors,
is_reliable: unsafe extern "C" fn(this: *const sys::IRecipientFilter) -> bool,
is_init_message: unsafe extern "C" fn(this: *const sys::IRecipientFilter) -> bool,
recipient_count: unsafe extern "C" fn(this: *const sys::IRecipientFilter) -> c_int,
recipient_index: unsafe extern "C" fn(this: *const sys::IRecipientFilter, slot: c_int) -> c_int,
}
const _: () = {
assert!(
offset_of!(RecipientFilterVtable, is_reliable)
== offset_of!(
sys::IRecipientFilter__bindgen_vtable,
IRecipientFilter_IsReliable
)
);
assert!(
offset_of!(RecipientFilterVtable, recipient_index)
== offset_of!(
sys::IRecipientFilter__bindgen_vtable,
IRecipientFilter_GetRecipientIndex
)
);
assert!(
size_of::<RecipientFilterVtable>() == size_of::<sys::IRecipientFilter__bindgen_vtable>()
);
};
impl<'a> RecipientFilter<'a> {
const VTABLE: RecipientFilterVtable = RecipientFilterVtable {
destructor: CppDestructors::new_noop(),
is_reliable: Self::is_reliable,
is_init_message: Self::is_init_message,
recipient_count: Self::recipient_count,
recipient_index: Self::recipient_index,
};
fn new(recipients: &'a Recipients) -> Self {
Self {
vtable: &Self::VTABLE,
recipients,
}
}
fn as_raw(&self) -> *mut sys::IRecipientFilter {
(&raw const *self).cast_mut().cast()
}
unsafe fn recipients<'b>(this: *const sys::IRecipientFilter) -> &'b Recipients {
unsafe { (*this.cast::<RecipientFilter<'b>>()).recipients }
}
unsafe extern "C" fn is_reliable(this: *const sys::IRecipientFilter) -> bool {
unsafe { Self::recipients(this) }.reliable
}
unsafe extern "C" fn is_init_message(_: *const sys::IRecipientFilter) -> bool {
false
}
unsafe extern "C" fn recipient_count(this: *const sys::IRecipientFilter) -> c_int {
let players = &unsafe { Self::recipients(this) }.players;
c_int::try_from(players.len()).unwrap_or(c_int::MAX)
}
unsafe extern "C" fn recipient_index(this: *const sys::IRecipientFilter, slot: c_int) -> c_int {
let players = &unsafe { Self::recipients(this) }.players;
usize::try_from(slot)
.ok()
.and_then(|slot| players.get(slot))
.copied()
.unwrap_or(-1)
}
}
#[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(std::ffi::CString),
#[error("the game registered {name:?} with {expected} bytes, but the payload has {bytes}")]
WrongSize {
name: std::ffi::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,
}
#[doc(alias = "UserMessageBegin")]
#[doc(alias = "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)?;
match registered.size {
Some(expected) if data.byte_len() != expected => {
return Err(UserMessageError::WrongSize {
name: name.to_owned(),
expected,
bytes: data.byte_len(),
});
}
_ => EncodeError::check_len("user message data", data.byte_len(), 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 = RecipientFilter::new(recipients);
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)
}
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 { RawBfWrite::append(buffer.cast(), data) };
unsafe { vcall!(engine => IVEngineServer_MessageEnd()) };
match copied {
true => Ok(()),
false => Err(UserMessageError::Overflow),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn filters_report_their_recipients_through_the_vtable() {
let recipients = Recipients {
players: vec![3, 7],
reliable: true,
};
let filter = RecipientFilter::new(&recipients);
let raw = filter.as_raw();
unsafe {
let vtable = (*raw).vtable_;
assert!(((*vtable).IRecipientFilter_IsReliable)(raw));
assert!(!((*vtable).IRecipientFilter_IsInitMessage)(raw));
assert_eq!(((*vtable).IRecipientFilter_GetRecipientCount)(raw), 2);
assert_eq!(((*vtable).IRecipientFilter_GetRecipientIndex)(raw, 1), 7);
assert_eq!(((*vtable).IRecipientFilter_GetRecipientIndex)(raw, 2), -1);
assert_eq!(((*vtable).IRecipientFilter_GetRecipientIndex)(raw, -1), -1);
}
}
}