#[cfg(test)]
#[path = "../tests/tf2/next_bot.rs"]
mod tests;
use crate::entities::Entity;
use crate::math::Vector;
use crate::tf2::bots::BotError;
use crate::tf2::nav::NavArea;
use crate::tf2::teams::Team;
use crate::{Game, Server};
use sdk_raw::tf2::nav::TEAM_ANY;
use sdk_raw::vcall;
use std::ffi::{CStr, c_int, c_uint};
use std::marker::PhantomData;
use std::ptr::{NonNull, null, null_mut};
pub(crate) unsafe fn next_bot_pointer(entity: Entity<'_>) -> Option<NonNull<sys::INextBot>> {
let raw = entity.as_ptr();
NonNull::new(unsafe {
vcall!(raw as sys::CBaseEntity__bindgen_vtable => CBaseEntity_MyNextBotPointer())
})
}
unsafe fn entity_of<'s>(server: Server<'s>, raw: *mut sys::CBaseEntity) -> Option<Entity<'s>> {
NonNull::new(raw).map(|raw| unsafe { Entity::from_live(server, raw) })
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Posture {
Stand,
Crouch,
Sit,
Crawl,
Lie,
}
impl Posture {
pub const ALL: [Self; 5] = [Self::Stand, Self::Crouch, Self::Sit, Self::Crawl, Self::Lie];
pub const fn from_raw(raw: sys::IBody_PostureType) -> Option<Self> {
match raw {
sys::IBody_PostureType_STAND => Some(Self::Stand),
sys::IBody_PostureType_CROUCH => Some(Self::Crouch),
sys::IBody_PostureType_SIT => Some(Self::Sit),
sys::IBody_PostureType_CRAWL => Some(Self::Crawl),
sys::IBody_PostureType_LIE => Some(Self::Lie),
_ => None,
}
}
pub const fn to_raw(self) -> sys::IBody_PostureType {
match self {
Self::Stand => sys::IBody_PostureType_STAND,
Self::Crouch => sys::IBody_PostureType_CROUCH,
Self::Sit => sys::IBody_PostureType_SIT,
Self::Crawl => sys::IBody_PostureType_CRAWL,
Self::Lie => sys::IBody_PostureType_LIE,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Arousal {
Neutral,
Alert,
Intense,
}
impl Arousal {
pub const ALL: [Self; 3] = [Self::Neutral, Self::Alert, Self::Intense];
pub const fn from_raw(raw: sys::IBody_ArousalType) -> Option<Self> {
match raw {
sys::IBody_ArousalType_NEUTRAL => Some(Self::Neutral),
sys::IBody_ArousalType_ALERT => Some(Self::Alert),
sys::IBody_ArousalType_INTENSE => Some(Self::Intense),
_ => None,
}
}
pub const fn to_raw(self) -> sys::IBody_ArousalType {
match self {
Self::Neutral => sys::IBody_ArousalType_NEUTRAL,
Self::Alert => sys::IBody_ArousalType_ALERT,
Self::Intense => sys::IBody_ArousalType_INTENSE,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum LookAtPriority {
Boring,
Interesting,
Important,
Critical,
Mandatory,
}
impl LookAtPriority {
pub const fn to_raw(self) -> sys::IBody_LookAtPriorityType {
match self {
Self::Boring => sys::IBody_LookAtPriorityType_BORING,
Self::Interesting => sys::IBody_LookAtPriorityType_INTERESTING,
Self::Important => sys::IBody_LookAtPriorityType_IMPORTANT,
Self::Critical => sys::IBody_LookAtPriorityType_CRITICAL,
Self::Mandatory => sys::IBody_LookAtPriorityType_MANDATORY,
}
}
}
const fn field_of_view_check(use_field_of_view: bool) -> sys::IVision_FieldOfViewCheckType {
if use_field_of_view {
sys::IVision_FieldOfViewCheckType_USE_FOV
} else {
sys::IVision_FieldOfViewCheckType_DISREGARD_FOV
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NextBotInterface<'s> {
bot: NonNull<sys::INextBot>,
_scope: PhantomData<&'s ()>,
}
impl<'s> NextBotInterface<'s> {
pub const unsafe fn from_raw(bot: NonNull<sys::INextBot>) -> Self {
Self {
bot,
_scope: PhantomData,
}
}
#[doc(alias("MyNextBotPointer"))]
pub fn of(server: Server<'s>, entity: Entity<'s>) -> Result<Option<Self>, BotError> {
if server.game() != Game::TeamFortress2 {
return Err(BotError::UnsupportedGame);
}
Ok(unsafe { next_bot_pointer(entity) }.map(|bot| unsafe { Self::from_raw(bot) }))
}
#[doc(alias("GetBodyInterface"))]
pub fn body(self) -> Body<'s> {
let bot = self.bot.as_ptr().cast_const();
let body =
unsafe { vcall!(bot as sys::INextBot__bindgen_vtable => INextBot_GetBodyInterface()) };
Body {
body: NonNull::new(body).expect("NextBot actors always have a body"),
_scope: PhantomData,
}
}
#[doc(alias("GetEntity"))]
pub fn entity(self, server: Server<'s>) -> Entity<'s> {
let bot = self.bot.as_ptr().cast_const();
let entity =
unsafe { vcall!(bot as sys::INextBot__bindgen_vtable => INextBot_GetEntity()) };
unsafe { entity_of(server, entity.cast()) }.expect("NextBot actors are entities")
}
#[doc(alias("GetLocomotionInterface"))]
pub fn locomotion(self) -> Locomotion<'s> {
let bot = self.bot.as_ptr().cast_const();
let locomotion = unsafe {
vcall!(bot as sys::INextBot__bindgen_vtable => INextBot_GetLocomotionInterface())
};
Locomotion {
locomotion: NonNull::new(locomotion).expect("NextBot actors always have a locomotion"),
_scope: PhantomData,
}
}
#[doc(alias("GetVisionInterface"))]
pub fn vision(self) -> Vision<'s> {
let bot = self.bot.as_ptr().cast_const();
let vision = unsafe {
vcall!(bot as sys::INextBot__bindgen_vtable => INextBot_GetVisionInterface())
};
Vision {
vision: NonNull::new(vision).expect("NextBot actors always have a vision"),
_scope: PhantomData,
}
}
pub const fn as_ptr(self) -> *mut sys::INextBot {
self.bot.as_ptr()
}
}
macro_rules! component_call {
($self:ident . $field:ident as $Vtable:ty => $method:ident($($argument:expr),* $(,)?)) => {{
let this = $self.$field.as_ptr();
unsafe { vcall!(this as $Vtable => $method($($argument),*)) }
}};
}
macro_rules! component_vector {
($self:ident . $field:ident as $Vtable:ty => $method:ident()) => {{
let vector = component_call!($self.$field as $Vtable => $method());
Vector::from(unsafe { vector.read() })
}};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Locomotion<'s> {
locomotion: NonNull<sys::ILocomotion>,
_scope: PhantomData<&'s ()>,
}
impl<'s> Locomotion<'s> {
#[doc(alias("Approach"))]
pub fn approach(self, goal: Vector, weight: f32) {
let goal = sys::Vector::from(goal);
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_Approach(&raw const goal, weight));
}
#[doc(alias("ClearStuckStatus"))]
pub fn clear_stuck_status(self, reason: &CStr) {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_ClearStuckStatus(reason.as_ptr()));
}
#[doc(alias("GetDeathDropHeight"))]
pub fn death_drop_height(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetDeathDropHeight())
}
#[doc(alias("GetDesiredSpeed"))]
pub fn desired_speed(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetDesiredSpeed())
}
#[doc(alias("DriveTo"))]
pub fn drive_to(self, position: Vector) {
let position = sys::Vector::from(position);
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_DriveTo(&raw const position));
}
#[doc(alias("FaceTowards"))]
pub fn face_towards(self, target: Vector) {
let target = sys::Vector::from(target);
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_FaceTowards(&raw const target));
}
#[doc(alias("GetFeet"))]
pub fn feet(self) -> Vector {
component_vector!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetFeet())
}
#[doc(alias("GetGround"))]
pub fn ground(self, server: Server<'s>) -> Option<Entity<'s>> {
let ground = component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetGround());
unsafe { entity_of(server, ground) }
}
#[doc(alias("GetGroundMotionVector"))]
pub fn ground_motion_vector(self) -> Vector {
component_vector!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetGroundMotionVector())
}
#[doc(alias("GetGroundNormal"))]
pub fn ground_normal(self) -> Vector {
component_vector!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetGroundNormal())
}
#[doc(alias("GetGroundSpeed"))]
pub fn ground_speed(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetGroundSpeed())
}
#[doc(alias("IsAreaTraversable"))]
pub fn is_area_traversable(self, area: NavArea<'_>) -> bool {
let area = area.as_ptr().cast::<sys::CNavArea>().cast_const();
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_IsAreaTraversable(area))
}
#[doc(alias("IsAttemptingToMove"))]
pub fn is_attempting_to_move(self) -> bool {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_IsAttemptingToMove())
}
#[doc(alias("IsClimbingOrJumping"))]
pub fn is_climbing_or_jumping(self) -> bool {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_IsClimbingOrJumping())
}
#[doc(alias("IsOnGround"))]
pub fn is_on_ground(self) -> bool {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_IsOnGround())
}
#[doc(alias("IsRunning"))]
pub fn is_running(self) -> bool {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_IsRunning())
}
#[doc(alias("IsStuck"))]
pub fn is_stuck(self) -> bool {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_IsStuck())
}
#[doc(alias("IsUsingLadder"))]
pub fn is_using_ladder(self) -> bool {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_IsUsingLadder())
}
#[doc(alias("Jump"))]
pub fn jump(self) {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_Jump());
}
#[doc(alias("GetMaxAcceleration"))]
pub fn max_acceleration(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetMaxAcceleration())
}
#[doc(alias("GetMaxDeceleration"))]
pub fn max_deceleration(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetMaxDeceleration())
}
#[doc(alias("GetMaxJumpHeight"))]
pub fn max_jump_height(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetMaxJumpHeight())
}
#[doc(alias("GetMotionVector"))]
pub fn motion_vector(self) -> Vector {
component_vector!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetMotionVector())
}
#[doc(alias("Run"))]
pub fn run(self) {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_Run());
}
#[doc(alias("GetRunSpeed"))]
pub fn run_speed(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetRunSpeed())
}
#[doc(alias("SetDesiredSpeed"))]
pub fn set_desired_speed(self, speed: f32) {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_SetDesiredSpeed(speed));
}
#[doc(alias("SetSpeedLimit"))]
pub fn set_speed_limit(self, speed: f32) {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_SetSpeedLimit(speed));
}
#[doc(alias("GetSpeed"))]
pub fn speed(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetSpeed())
}
#[doc(alias("GetSpeedLimit"))]
pub fn speed_limit(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetSpeedLimit())
}
#[doc(alias("GetStepHeight"))]
pub fn step_height(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetStepHeight())
}
#[doc(alias("Stop"))]
pub fn stop(self) {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_Stop());
}
#[doc(alias("GetStuckDuration"))]
pub fn stuck_duration(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetStuckDuration())
}
#[doc(alias("GetTraversableSlopeLimit"))]
pub fn traversable_slope_limit(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetTraversableSlopeLimit())
}
#[doc(alias("GetVelocity"))]
pub fn velocity(self) -> Vector {
component_vector!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetVelocity())
}
#[doc(alias("Walk"))]
pub fn walk(self) {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_Walk());
}
#[doc(alias("GetWalkSpeed"))]
pub fn walk_speed(self) -> f32 {
component_call!(self.locomotion as sys::ILocomotion__bindgen_vtable => ILocomotion_GetWalkSpeed())
}
pub const fn as_ptr(self) -> *mut sys::ILocomotion {
self.locomotion.as_ptr()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Body<'s> {
body: NonNull<sys::IBody>,
_scope: PhantomData<&'s ()>,
}
impl<'s> Body<'s> {
#[doc(alias("AimHeadTowards"))]
pub fn aim_head_at(self, subject: Entity<'_>, priority: LookAtPriority, duration: f32) {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_AimHeadTowards1(
subject.as_ptr(),
priority.to_raw(),
duration,
null_mut(),
null(),
));
}
#[doc(alias("AimHeadTowards"))]
pub fn aim_head_towards(self, position: Vector, priority: LookAtPriority, duration: f32) {
let position = sys::Vector::from(position);
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_AimHeadTowards(
&raw const position,
priority.to_raw(),
duration,
null_mut(),
null(),
));
}
#[doc(alias("GetArousal"))]
pub fn arousal(self) -> Option<Arousal> {
Arousal::from_raw(
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_GetArousal()),
)
}
#[doc(alias("GetActualPosture"))]
pub fn actual_posture(self) -> Option<Posture> {
Posture::from_raw(
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_GetActualPosture()),
)
}
#[doc(alias("GetCollisionGroup"))]
pub fn collision_group(self) -> c_uint {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_GetCollisionGroup())
}
#[doc(alias("GetCrouchHullHeight"))]
pub fn crouch_hull_height(self) -> f32 {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_GetCrouchHullHeight())
}
#[doc(alias("GetDesiredPosture"))]
pub fn desired_posture(self) -> Option<Posture> {
Posture::from_raw(
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_GetDesiredPosture()),
)
}
#[doc(alias("GetEyePosition"))]
pub fn eye_position(self) -> Vector {
component_vector!(self.body as sys::IBody__bindgen_vtable => IBody_GetEyePosition())
}
#[doc(alias("GetHullMins", "GetHullMaxs"))]
pub fn hull(self) -> (Vector, Vector) {
(
component_vector!(self.body as sys::IBody__bindgen_vtable => IBody_GetHullMins()),
component_vector!(self.body as sys::IBody__bindgen_vtable => IBody_GetHullMaxs()),
)
}
#[doc(alias("GetHullHeight"))]
pub fn hull_height(self) -> f32 {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_GetHullHeight())
}
#[doc(alias("GetHullWidth"))]
pub fn hull_width(self) -> f32 {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_GetHullWidth())
}
#[doc(alias("IsHeadAimingOnTarget"))]
pub fn is_head_aiming_on_target(self) -> bool {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_IsHeadAimingOnTarget())
}
#[doc(alias("IsInDesiredPosture"))]
pub fn is_in_desired_posture(self) -> bool {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_IsInDesiredPosture())
}
#[doc(alias("IsPostureMobile"))]
pub fn is_posture_mobile(self) -> bool {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_IsPostureMobile())
}
#[doc(alias("SetArousal"))]
pub fn set_arousal(self, arousal: Arousal) {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_SetArousal(arousal.to_raw()));
}
#[doc(alias("SetDesiredPosture"))]
pub fn set_desired_posture(self, posture: Posture) {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_SetDesiredPosture(posture.to_raw()));
}
#[doc(alias("GetSolidMask"))]
pub fn solid_mask(self) -> c_uint {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_GetSolidMask())
}
#[doc(alias("GetStandHullHeight"))]
pub fn stand_hull_height(self) -> f32 {
component_call!(self.body as sys::IBody__bindgen_vtable => IBody_GetStandHullHeight())
}
#[doc(alias("GetViewVector"))]
pub fn view_vector(self) -> Vector {
component_vector!(self.body as sys::IBody__bindgen_vtable => IBody_GetViewVector())
}
pub const fn as_ptr(self) -> *mut sys::IBody {
self.body.as_ptr()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Vision<'s> {
vision: NonNull<sys::IVision>,
_scope: PhantomData<&'s ()>,
}
impl<'s> Vision<'s> {
#[doc(alias("AddKnownEntity"))]
pub fn add_known_entity(self, entity: Entity<'_>) {
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_AddKnownEntity(entity.as_ptr()));
}
#[doc(alias("GetFieldOfView"))]
pub fn field_of_view(self) -> f32 {
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_GetFieldOfView())
}
#[doc(alias("ForgetAllKnownEntities"))]
pub fn forget_all_known_entities(self) {
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_ForgetAllKnownEntities());
}
#[doc(alias("ForgetEntity"))]
pub fn forget_entity(self, entity: Entity<'_>) {
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_ForgetEntity(entity.as_ptr()));
}
#[doc(alias("IsAbleToSee"))]
pub fn is_able_to_see(self, entity: Entity<'_>, use_field_of_view: bool) -> bool {
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_IsAbleToSee(
entity.as_ptr(),
field_of_view_check(use_field_of_view),
null_mut(),
))
}
#[doc(alias("IsAbleToSee"))]
pub fn is_able_to_see_position(self, position: Vector, use_field_of_view: bool) -> bool {
let position = sys::Vector::from(position);
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_IsAbleToSee1(
&raw const position,
field_of_view_check(use_field_of_view),
))
}
#[doc(alias("IsInFieldOfView"))]
pub fn is_in_field_of_view(self, position: Vector) -> bool {
let position = sys::Vector::from(position);
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_IsInFieldOfView(&raw const position))
}
#[doc(alias("IsLineOfSightClear"))]
pub fn is_line_of_sight_clear(self, position: Vector) -> bool {
let position = sys::Vector::from(position);
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_IsLineOfSightClear(&raw const position))
}
#[doc(alias("GetKnownCount"))]
pub fn known_count(self, team: Option<Team>, only_visible: bool, range: Option<f32>) -> c_int {
let team = team.map_or(TEAM_ANY, Team::to_raw);
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_GetKnownCount(
team,
only_visible,
range.unwrap_or(-1.0),
))
}
#[doc(alias("GetMaxVisionRange"))]
pub fn max_vision_range(self) -> f32 {
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_GetMaxVisionRange())
}
#[doc(alias("GetPrimaryKnownThreat"))]
pub fn primary_known_threat(
self,
server: Server<'s>,
only_visible: bool,
) -> Option<Entity<'s>> {
let known = component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_GetPrimaryKnownThreat(only_visible));
if known.is_null() {
return None;
}
let entity = unsafe {
vcall!(known as sys::CKnownEntity__bindgen_vtable => CKnownEntity_GetEntity())
};
unsafe { entity_of(server, entity) }
}
#[doc(alias("SetFieldOfView"))]
pub fn set_field_of_view(self, degrees: f32) {
component_call!(self.vision as sys::IVision__bindgen_vtable => IVision_SetFieldOfView(degrees));
}
pub const fn as_ptr(self) -> *mut sys::IVision {
self.vision.as_ptr()
}
}