#[cfg(test)]
#[path = "tests/server.rs"]
mod tests;
use crate::NotThreadSafe;
use crate::interfaces::{
BotManager, Cvar, EngineReplay, EngineSound, EngineTrace, GameEventManager, ModelInfo,
NetworkStringTables, PlayerInfoManager, PluginHelpers, ServerGameClients, ServerGameDll,
ServerGameEnts, ServerGameTags, ServerTools, ValveEngine, VoiceServer,
};
use sdk_raw::entities::TeleportSlot;
use sdk_raw::interfaces::{CreateInterfaceFn, create_interface};
#[cfg(test)]
use std::cell::Cell;
use std::ffi::{CStr, CString};
use std::fmt::{self, Display, Formatter};
use std::marker::PhantomData;
use std::ptr::NonNull;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Game {
TeamFortress2,
SourceSdk2013,
SourceSdk2013NextBot,
}
impl Game {
pub(crate) const fn teleport_vtable_slot(self) -> TeleportSlot {
match self {
Self::TeamFortress2 => TeleportSlot::TeamFortress2,
Self::SourceSdk2013 => TeleportSlot::SourceSdk2013,
Self::SourceSdk2013NextBot => TeleportSlot::SourceSdk2013NextBot,
}
}
}
pub(crate) unsafe trait Interface<'s>: Sized {
type Raw;
const MODULE: Module;
const VERSION: &'static CStr;
unsafe fn bind(raw: NonNull<Self::Raw>, server: &Server<'s>) -> Self;
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("the {module} does not export `{}`; the server may not match the SDK the bindings were generated from", .version.to_string_lossy())]
pub struct InterfaceError {
module: Module,
version: CString,
}
impl InterfaceError {
pub const fn module(&self) -> Module {
self.module
}
pub fn version(&self) -> &CStr {
&self.version
}
}
#[doc(alias("CreateInterface", "CreateInterfaceFn"))]
#[derive(Debug, Clone, Copy)]
pub struct InterfaceFactory(CreateInterfaceFn);
impl InterfaceFactory {
pub const fn new(factory: CreateInterfaceFn) -> Self {
Self(factory)
}
pub const fn from_raw(factory: sys::CreateInterfaceFn) -> Option<Self> {
match factory {
Some(factory) => Some(Self(factory)),
None => None,
}
}
pub const fn as_raw(self) -> CreateInterfaceFn {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Module {
Engine,
GameServer,
}
impl Display for Module {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Engine => "engine",
Self::GameServer => "game server",
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct Server<'s> {
engine: InterfaceFactory,
game_server: InterfaceFactory,
game: Game,
_scope: PhantomData<&'s ()>,
_not_thread_safe: NotThreadSafe,
}
impl<'s> Server<'s> {
pub const unsafe fn new<S: ?Sized>(
engine: InterfaceFactory,
game_server: InterfaceFactory,
game: Game,
_scope: &'s S,
) -> Self {
Self {
engine,
game_server,
game,
_scope: PhantomData,
_not_thread_safe: PhantomData,
}
}
pub fn bot_manager(&self) -> Result<BotManager<'s>, InterfaceError> {
self.interface()
}
#[doc(alias("Msg"))]
pub fn console_print(&self, message: &CStr) {
if tier0_print(message) {
return;
}
if let Ok(cvar) = self.cvar() {
cvar.console_printf(message);
}
}
pub fn cvar(&self) -> Result<Cvar<'s>, InterfaceError> {
self.interface()
}
pub const fn engine_factory(&self) -> InterfaceFactory {
self.engine
}
pub fn engine_replay(&self) -> Result<EngineReplay<'s>, InterfaceError> {
self.interface()
}
pub fn engine_sound(&self) -> Result<EngineSound<'s>, InterfaceError> {
self.interface()
}
pub fn engine_trace(&self) -> Result<EngineTrace<'s>, InterfaceError> {
self.interface()
}
pub fn find_interface<T>(
&self,
module: Module,
version: &CStr,
) -> Result<NonNull<T>, InterfaceError> {
let factory = match module {
Module::Engine => self.engine,
Module::GameServer => self.game_server,
};
let interface = unsafe { create_interface(factory.as_raw(), version) };
interface.map(NonNull::cast).ok_or_else(|| InterfaceError {
module,
version: version.to_owned(),
})
}
pub const fn game(&self) -> Game {
self.game
}
pub fn game_events(&self) -> Result<GameEventManager<'s>, InterfaceError> {
self.interface()
}
pub const fn game_server_factory(&self) -> InterfaceFactory {
self.game_server
}
fn interface<I: Interface<'s>>(&self) -> Result<I, InterfaceError> {
let raw = self.find_interface::<I::Raw>(I::MODULE, I::VERSION)?;
Ok(unsafe { I::bind(raw, self) })
}
pub fn model_info(&self) -> Result<ModelInfo<'s>, InterfaceError> {
self.interface()
}
pub fn network_string_tables(&self) -> Result<NetworkStringTables<'s>, InterfaceError> {
self.interface()
}
pub fn player_info_manager(&self) -> Result<PlayerInfoManager<'s>, InterfaceError> {
self.interface()
}
pub fn plugin_helpers(&self) -> Result<PluginHelpers<'s>, InterfaceError> {
self.interface()
}
pub fn server_game_clients(&self) -> Result<ServerGameClients<'s>, InterfaceError> {
self.interface()
}
pub fn server_game_dll(&self) -> Result<ServerGameDll<'s>, InterfaceError> {
self.interface()
}
pub fn server_game_ents(&self) -> Result<ServerGameEnts<'s>, InterfaceError> {
self.interface()
}
pub fn server_game_tags(&self) -> Result<ServerGameTags<'s>, InterfaceError> {
self.interface()
}
pub fn server_tools(&self) -> Result<ServerTools<'s>, InterfaceError> {
self.interface()
}
pub fn valve_engine(&self) -> Result<ValveEngine<'s>, InterfaceError> {
self.interface()
}
pub fn voice_server(&self) -> Result<VoiceServer<'s>, InterfaceError> {
self.interface()
}
}
#[cfg(test)]
thread_local! {
pub(crate) static TEST_MSG: Cell<Option<sdk_raw::tier0::MsgFn>> = const { Cell::new(None) };
}
#[derive(Debug, Clone, Copy)]
pub struct ServerBinding {
engine: InterfaceFactory,
game_server: InterfaceFactory,
game: Game,
_not_thread_safe: NotThreadSafe,
}
impl ServerBinding {
pub const unsafe fn new(
engine: InterfaceFactory,
game_server: InterfaceFactory,
game: Game,
) -> Self {
Self {
engine,
game_server,
game,
_not_thread_safe: PhantomData,
}
}
pub const fn game(&self) -> Game {
self.game
}
pub const unsafe fn server<'s, S: ?Sized>(&self, scope: &'s S) -> Server<'s> {
unsafe { Server::new(self.engine, self.game_server, self.game, scope) }
}
}
fn tier0_print(message: &CStr) -> bool {
#[cfg(test)]
if let Some(msg) = TEST_MSG.get() {
unsafe { sdk_raw::tier0::print_through(msg, message) };
return true;
}
unsafe { sdk_raw::tier0::print(message) }
}