#[cfg(test)]
#[path = "../tests/entities/search.rs"]
mod tests;
use crate::entities::Entity;
use crate::interfaces::ServerTools;
use crate::math::Vector;
use sdk_raw::vcall;
use std::ffi::CStr;
use std::iter;
use std::ptr::{self, NonNull};
impl<'s> Entity<'s> {
#[doc(alias("NameMatches", "NamesMatch"))]
pub fn name_matches(self, query: &CStr) -> bool {
if query.to_bytes().eq_ignore_ascii_case(b"!player") {
return self.is_a(c"CBasePlayer");
}
let Some(field) = self.name_field() else {
return false;
};
let name = unsafe { field.read() }.pszValue;
if name.is_null() {
return matches!(query.to_bytes(), [] | [b'*', ..]);
}
names_match(query.to_bytes(), unsafe { CStr::from_ptr(name) }.to_bytes())
}
}
impl<'s> ServerTools<'s> {
#[doc(alias("FindEntityByClassname"))]
pub fn entities_by_class(
self,
class_name: &CStr,
) -> impl Iterator<Item = Entity<'s>> + use<'s, '_> {
iter::successors(self.find_by_class_name(None, class_name), move |&entity| {
self.find_by_class_name(Some(entity), class_name)
})
}
#[doc(alias("FindEntityByModel"))]
pub fn entities_by_model(self, model: &CStr) -> impl Iterator<Item = Entity<'s>> + use<'s, '_> {
let find = move |after: Option<Entity<'s>>| {
let after = after.map_or(ptr::null_mut(), Entity::as_ptr);
let entity = unsafe {
vcall!(self.as_ptr() => IServerTools_FindEntityByModel(after, model.as_ptr()))
};
NonNull::new(entity).map(|entity| unsafe { Entity::from_raw(entity) })
};
iter::successors(find(None), move |&entity| find(Some(entity)))
}
#[doc(alias("FindEntityInSphere"))]
pub fn entities_in_sphere(
self,
center: Vector,
radius: f32,
) -> impl Iterator<Item = Entity<'s>> + use<'s> {
let center = sys::Vector::from(center);
let find = move |after: Option<Entity<'s>>| {
let after = after.map_or(ptr::null_mut(), Entity::as_ptr);
let entity = unsafe {
vcall!(self.as_ptr() => IServerTools_FindEntityInSphere(after, &raw const center, radius))
};
NonNull::new(entity).map(|entity| unsafe { Entity::from_raw(entity) })
};
iter::successors(find(None), move |&entity| find(Some(entity)))
}
}
fn characters_match(name: u8, query: u8) -> bool {
let (name, query) = (i32::from(name), i32::from(query));
let upper = i32::from(b'A');
let lower = i32::from(b'a');
name == query
|| (name - upper <= 25 && name - upper + lower == query)
|| (name - lower <= 25 && name - lower + upper == query)
}
fn names_match(query: &[u8], name: &[u8]) -> bool {
let common = query
.iter()
.zip(name)
.take_while(|&(&query, &name)| characters_match(name, query))
.count();
matches!(
(&query[common..], &name[common..]),
([], []) | ([b'*', ..], _)
)
}