use crate::edicts::Edict;
use crate::math::Vector;
use crate::net::NetChannel;
use crate::players::UserId;
use sdk_raw::edicts::MAX_EDICTS;
use sdk_raw::players::ABSOLUTE_PLAYER_LIMIT;
use sdk_raw::tier0::MAX_PATH;
use sdk_raw::util::cstr::{copy_cstr, cstring_from_buffer};
use sdk_raw::vcall;
use std::ffi::{CStr, CString, c_char, c_int};
use std::marker::PhantomData;
use std::ptr::{self, NonNull};
interface! {
#[doc(alias("IVEngineServer"))]
pub struct ValveEngine(sys::IVEngineServer) = Engine sdk_raw::interfaces::valve_engine::VERSION;
}
impl<'s> ValveEngine<'s> {
#[doc(alias("GetChangeAccessor"))]
pub(crate) fn change_accessor(
self,
edict: Edict<'_>,
) -> Option<NonNull<sys::IChangeInfoAccessor>> {
NonNull::new(unsafe {
vcall!(self.as_ptr() => IVEngineServer_GetChangeAccessor(edict.as_ptr()))
})
}
#[doc(alias("ChangeLevel"))]
pub fn change_level(self, map: &CStr, landmark: Option<&CStr>) {
let landmark = landmark.map_or(ptr::null(), CStr::as_ptr);
unsafe { vcall!(self.as_ptr() => IVEngineServer_ChangeLevel(map.as_ptr(), landmark)) };
}
#[doc(alias("CheckBoxInPVS"))]
pub fn check_box_in_pvs(self, mins: Vector, maxs: Vector, pvs: &Pvs<'_>) -> bool {
if pvs.bits.is_empty() {
return false;
}
let mins = sys::Vector::from(mins);
let maxs = sys::Vector::from(maxs);
unsafe {
vcall!(self.as_ptr() => IVEngineServer_CheckBoxInPVS(&mins, &maxs, pvs.bits.as_ptr(), pvs.len_c_int()))
}
}
#[doc(alias("CheckOriginInPVS"))]
pub fn check_origin_in_pvs(self, origin: Vector, pvs: &Pvs<'_>) -> bool {
if pvs.bits.is_empty() {
return false;
}
let origin = sys::Vector::from(origin);
unsafe {
vcall!(self.as_ptr() => IVEngineServer_CheckOriginInPVS(&origin, pvs.bits.as_ptr(), pvs.len_c_int()))
}
}
#[doc(alias("GetClientConVarValue"))]
pub fn client_convar_value(self, client: Edict<'_>, name: &CStr) -> Option<CString> {
unsafe {
copy_cstr(
vcall!(self.as_ptr() => IVEngineServer_GetClientConVarValue(client.index(), name.as_ptr())),
)
}
}
#[doc(alias("ClientPrintf"))]
pub fn client_print(self, client: Edict<'_>, message: &CStr) {
unsafe {
vcall!(self.as_ptr() => IVEngineServer_ClientPrintf(client.as_ptr(), message.as_ptr()))
};
}
#[doc(alias("GetClientSteamID"))]
pub fn client_steam_id(self, client: Edict<'_>) -> Option<u64> {
let steam_id =
unsafe { vcall!(self.as_ptr() => IVEngineServer_GetClientSteamID(client.as_ptr())) };
let steam_id = unsafe { steam_id.as_ref()?.m_steamid.m_unAll64Bits };
is_valid_steam_id(steam_id).then_some(steam_id)
}
#[doc(alias("GetClusterForOrigin"))]
pub fn cluster_for_origin(self, origin: Vector) -> Option<Cluster> {
let origin = sys::Vector::from(origin);
let cluster =
unsafe { vcall!(self.as_ptr() => IVEngineServer_GetClusterForOrigin(&origin)) };
(cluster >= 0).then_some(Cluster(cluster))
}
#[doc(alias("CrosshairAngle"))]
pub fn crosshair_angle(self, client: Edict<'_>, pitch: f32, yaw: f32) {
unsafe {
vcall!(self.as_ptr() => IVEngineServer_CrosshairAngle(client.as_ptr(), pitch, yaw))
};
}
#[doc(alias("PEntityOfEntIndex", "INDEXENT"))]
pub fn edict_of_index(self, index: c_int) -> Option<Edict<'s>> {
if !(0..MAX_EDICTS).contains(&index) {
return None;
}
let edict = NonNull::new(unsafe {
vcall!(self.as_ptr() => IVEngineServer_PEntityOfEntIndex(index))
})?;
Some(unsafe { Edict::from_raw(edict) })
}
pub fn edict_of_user_id(self, user_id: UserId) -> Option<Edict<'s>> {
(1..=ABSOLUTE_PLAYER_LIMIT)
.filter_map(|index| self.edict_of_index(index))
.find(|&edict| !edict.is_free() && self.user_id_of_edict(edict) == Some(user_id))
}
#[doc(alias("GetEntityCount"))]
pub fn entity_count(self) -> c_int {
unsafe { vcall!(self.as_ptr() => IVEngineServer_GetEntityCount()) }
}
#[doc(alias("FindMap"))]
pub fn find_map(self, name: &CStr) -> Option<(CString, FoundMap)> {
let name = name.to_bytes();
let mut buffer = [0 as c_char; MAX_PATH];
if name.len() >= MAX_PATH {
return None;
}
for (to, &from) in buffer.iter_mut().zip(name) {
*to = from as c_char;
}
let found = unsafe {
vcall!(self.as_ptr() => IVEngineServer_FindMap(buffer.as_mut_ptr(), MAX_PATH as c_int))
};
let found = match found {
sys::IVEngineServer_eFindMapResult_eFindMap_Found => FoundMap::Exact,
sys::IVEngineServer_eFindMapResult_eFindMap_FuzzyMatch => FoundMap::Fuzzy,
sys::IVEngineServer_eFindMapResult_eFindMap_NonCanonical => FoundMap::NonCanonical,
sys::IVEngineServer_eFindMapResult_eFindMap_PossiblyAvailable => {
FoundMap::PossiblyAvailable
}
_ => return None,
};
Some((cstring_from_buffer(&buffer), found))
}
#[doc(alias("GetGameDir"))]
pub fn game_dir(self) -> CString {
let mut buffer = [0 as c_char; MAX_PATH];
unsafe {
vcall!(self.as_ptr() => IVEngineServer_GetGameDir(buffer.as_mut_ptr(), MAX_PATH as c_int))
};
cstring_from_buffer(&buffer)
}
#[doc(alias("IsMapValid"))]
pub fn is_map_valid(self, file: &CStr) -> bool {
unsafe { vcall!(self.as_ptr() => IVEngineServer_IsMapValid(file.as_ptr())) != 0 }
}
#[doc(alias("LockNetworkStringTables"))]
pub fn lock_network_string_tables(self, lock: bool) -> bool {
unsafe { vcall!(self.as_ptr() => IVEngineServer_LockNetworkStringTables(lock)) }
}
#[doc(alias("LogPrint"))]
pub fn log_print(self, message: &CStr) {
unsafe { vcall!(self.as_ptr() => IVEngineServer_LogPrint(message.as_ptr())) };
}
#[doc(alias("GetMapEntitiesString"))]
pub fn map_entities(self) -> Option<CString> {
unsafe { copy_cstr(vcall!(self.as_ptr() => IVEngineServer_GetMapEntitiesString())) }
.filter(|entities| !entities.is_empty())
}
#[doc(alias("GetPlayerNetInfo"))]
pub fn net_channel(self, client: Edict<'_>) -> Option<NetChannel<'s>> {
let info = NonNull::new(unsafe {
vcall!(self.as_ptr() => IVEngineServer_GetPlayerNetInfo(client.index()))
})?;
Some(unsafe { NetChannel::from_raw(info.cast()) })
}
#[doc(alias("NotifyEdictFlagsChange"))]
pub(crate) fn notify_edict_flags_change(self, edict: Edict<'_>) {
unsafe { vcall!(self.as_ptr() => IVEngineServer_NotifyEdictFlagsChange(edict.index())) };
}
#[doc(alias("GetPlayerNetworkIDString"))]
pub fn player_network_id(self, edict: Edict<'_>) -> Option<CString> {
unsafe {
copy_cstr(
vcall!(self.as_ptr() => IVEngineServer_GetPlayerNetworkIDString(edict.as_ptr())),
)
}
}
#[doc(alias("GetPVSForCluster"))]
pub fn pvs_for_cluster(self, cluster: Cluster) -> Pvs<'s> {
self.pvs_for_cluster_index(cluster.0)
}
fn pvs_for_cluster_index(self, cluster: c_int) -> Pvs<'s> {
let mut bits = vec![0u8; PVS_CAPACITY].into_boxed_slice();
let length = unsafe {
vcall!(self.as_ptr() => IVEngineServer_GetPVSForCluster(cluster, PVS_CAPACITY as c_int, bits.as_mut_ptr()))
};
let length = usize::try_from(length).unwrap_or(0).min(PVS_CAPACITY);
Pvs {
bits: bits[..length].into(),
_level: PhantomData,
}
}
pub fn pvs_for_origin(self, origin: Vector) -> Pvs<'s> {
self.pvs_for_cluster_index(
self.cluster_for_origin(origin)
.map_or(-1, |cluster| cluster.0),
)
}
#[doc(alias("ServerCommand"))]
pub fn server_command(self, command: &CStr) {
unsafe { vcall!(self.as_ptr() => IVEngineServer_ServerCommand(command.as_ptr())) };
}
#[doc(alias("ServerExecute"))]
pub fn server_execute(self) {
unsafe { vcall!(self.as_ptr() => IVEngineServer_ServerExecute()) };
}
#[doc(alias("SetView"))]
pub fn set_view(self, client: Edict<'_>, view: Edict<'_>) {
unsafe { vcall!(self.as_ptr() => IVEngineServer_SetView(client.as_ptr(), view.as_ptr())) };
}
#[doc(alias("GetSharedEdictChangeInfo"))]
pub(crate) fn shared_edict_change_info(self) -> Option<NonNull<sys::CSharedEdictChangeInfo>> {
NonNull::new(unsafe { vcall!(self.as_ptr() => IVEngineServer_GetSharedEdictChangeInfo()) })
}
#[doc(alias("GetPlayerUserId"))]
pub fn user_id_of_edict(self, edict: Edict<'_>) -> Option<UserId> {
UserId::from_raw(unsafe {
vcall!(self.as_ptr() => IVEngineServer_GetPlayerUserId(edict.as_ptr()))
})
.ok()
}
#[doc(alias("LockNetworkStringTables"))]
pub fn with_unlocked_string_tables<R>(self, f: impl FnOnce() -> R) -> R {
struct Restore<'s> {
engine: ValveEngine<'s>,
locked: bool,
}
impl Drop for Restore<'_> {
fn drop(&mut self) {
self.engine.lock_network_string_tables(self.locked);
}
}
let _restore = Restore {
engine: self,
locked: self.lock_network_string_tables(false),
};
f()
}
}
const PVS_CAPACITY: usize = 65536 / 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Cluster(c_int);
impl Cluster {
pub const fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[doc(alias("eFindMapResult"))]
pub enum FoundMap {
Exact,
Fuzzy,
NonCanonical,
PossiblyAvailable,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Pvs<'s> {
bits: Box<[u8]>,
_level: PhantomData<ValveEngine<'s>>,
}
impl Pvs<'_> {
pub fn as_bytes(&self) -> &[u8] {
&self.bits
}
pub fn contains(&self, cluster: Cluster) -> bool {
let index = cluster.index();
self.bits
.get(index / 8)
.is_some_and(|byte| byte & (1 << (index % 8)) != 0)
}
pub fn is_empty(&self) -> bool {
self.bits.iter().all(|&byte| byte == 0)
}
fn len_c_int(&self) -> c_int {
self.bits.len() as c_int
}
pub fn union_with(&mut self, other: &Pvs<'_>) {
if other.bits.len() > self.bits.len() {
let mut bits = vec![0u8; other.bits.len()].into_boxed_slice();
bits[..self.bits.len()].copy_from_slice(&self.bits);
self.bits = bits;
}
for (byte, other) in self.bits.iter_mut().zip(&other.bits) {
*byte |= other;
}
}
}
fn is_valid_steam_id(steam_id: u64) -> bool {
let account = steam_id as u32;
let instance = (steam_id >> 32) as u32 & 0xF_FFFF;
let account_type = (steam_id >> 52) as u32 & 0xF;
let universe = (steam_id >> 56) as u32;
let known_universe = (1..=4).contains(&universe);
known_universe
&& match account_type {
1 => account != 0 && instance == 1,
3 => account != 0,
7 => account != 0 && instance == 0,
2..=10 => true,
_ => false,
}
}