#[cfg(test)]
#[path = "../tests/tf2/buildings.rs"]
mod tests;
use crate::datatables::{NetProp, NetPropError, NetVar};
use crate::entities::Entity;
use crate::entities::spawn::{EntitySpawn, SpawnError};
use crate::inputs::{InputError, InputValue};
use crate::math::{QAngle, Vector};
use crate::tf2::objects::ObjectKind;
use crate::tf2::script_binding::{self as binding, BindingError};
use crate::tf2::teams::Team;
use crate::{Game, InterfaceError, Server};
use sdk_raw::tf2::buildings as raw;
use sdk_raw::tf2::script_binding::{BOOL, boolean};
use sdk_raw::vcall;
use std::ffi::{CStr, CString, c_int};
const CLASSES: [&CStr; 4] = [
c"obj_*",
c"mapobj_cart_dispenser",
c"pd_dispenser",
c"rd_robot_dispenser",
];
const SAPPER_CLASS: &CStr = c"obj_attachment_sapper";
#[derive(Debug, thiserror::Error)]
pub enum BuildingError {
#[error(transparent)]
Binding(#[from] BindingError),
#[error("the building is a blueprint its builder is placing")]
Blueprint,
#[error("the building is being destroyed")]
Dying,
#[error("the building already has a builder")]
HasBuilder,
#[error(transparent)]
Input(#[from] InputError),
#[error(transparent)]
Interface(#[from] InterfaceError),
#[error("buildings have levels 1 to 3, not {0}")]
InvalidLevel(u8),
#[error("the entity is marked for deletion")]
MarkedForDeletion,
#[error(transparent)]
NetProp(#[from] NetPropError),
#[error("the entity is not a TF2 building")]
NotBuilding,
#[error("the building is not a {0:?}")]
NotKind(BuildingKind),
#[error("the entity is not a TF2 player")]
NotTfPlayer,
#[error(transparent)]
Spawn(#[from] SpawnError),
#[error("the building's type {0} is not one of TF2's")]
UnknownKind(c_int),
#[error("the building's state {0} is not one of its kind's")]
UnknownState(c_int),
#[error("building operations require Team Fortress 2")]
WrongGame,
}
#[doc(alias("ObjectType_t", "GetType", "m_iObjectType"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuildingKind {
#[doc(alias("OBJ_DISPENSER", "CObjectDispenser", "obj_dispenser"))]
Dispenser,
#[doc(alias("OBJ_ATTACHMENT_SAPPER", "CObjectSapper", "obj_attachment_sapper"))]
Sapper,
#[doc(alias("OBJ_SENTRYGUN", "CObjectSentrygun", "obj_sentrygun"))]
Sentry,
#[doc(alias("OBJ_TELEPORTER", "CObjectTeleporter", "obj_teleporter"))]
Teleporter,
}
impl BuildingKind {
pub const ALL: [Self; 4] = [
Self::Dispenser,
Self::Teleporter,
Self::Sentry,
Self::Sapper,
];
pub const fn from_raw(raw: c_int) -> Option<Self> {
Some(match raw {
raw::OBJ_DISPENSER => Self::Dispenser,
raw::OBJ_TELEPORTER => Self::Teleporter,
raw::OBJ_SENTRYGUN => Self::Sentry,
raw::OBJ_ATTACHMENT_SAPPER => Self::Sapper,
_ => return None,
})
}
pub const fn object_kind(self) -> Option<ObjectKind> {
match self {
Self::Dispenser => Some(ObjectKind::Dispenser),
Self::Sentry => Some(ObjectKind::Sentry),
Self::Teleporter => Some(ObjectKind::Teleporter),
Self::Sapper => None,
}
}
pub const fn to_raw(self) -> c_int {
match self {
Self::Dispenser => raw::OBJ_DISPENSER,
Self::Teleporter => raw::OBJ_TELEPORTER,
Self::Sentry => raw::OBJ_SENTRYGUN,
Self::Sapper => raw::OBJ_ATTACHMENT_SAPPER,
}
}
}
impl From<ObjectKind> for BuildingKind {
fn from(kind: ObjectKind) -> Self {
match kind {
ObjectKind::Dispenser => Self::Dispenser,
ObjectKind::Sentry => Self::Sentry,
ObjectKind::Teleporter => Self::Teleporter,
}
}
}
bitflags::bitflags! {
#[doc(alias("m_fObjectFlags", "GetObjectFlags"))]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ObjectFlags: c_int {
#[doc(alias("OF_ALLOW_REPEAT_PLACEMENT"))]
const ALLOW_REPEAT_PLACEMENT = raw::OF_ALLOW_REPEAT_PLACEMENT;
#[doc(alias("OF_MUST_BE_BUILT_ON_ATTACHMENT"))]
const BUILT_ON_ATTACHMENT = raw::OF_MUST_BE_BUILT_ON_ATTACHMENT;
#[doc(alias("OF_DOESNT_HAVE_A_MODEL"))]
const NO_MODEL = raw::OF_DOESNT_HAVE_A_MODEL;
#[doc(alias("OF_PLAYER_DESTRUCTION"))]
const PLAYER_DESTRUCTION = raw::OF_PLAYER_DESTRUCTION;
const _ = !0;
}
}
use crate::tf2::class_targets::{ClassTarget, ClassTargetError, ClassTargets};
use sdk_raw::util;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::ptr::NonNull;
#[doc(alias("CBaseObject"))]
#[derive(Debug, Clone, Copy)]
pub struct Building<'s> {
server: Server<'s>,
entity: Entity<'s>,
}
impl<'s> Building<'s> {
pub fn new(server: Server<'s>, entity: Entity<'s>) -> Result<Self, BuildingError> {
if server.game() != Game::TeamFortress2 {
return Err(BuildingError::WrongGame);
}
if !entity.has_data_map_class(c"CBaseObject") {
return Err(BuildingError::NotBuilding);
}
Ok(Self { server, entity })
}
pub fn all(server: Server<'s>) -> Result<impl Iterator<Item = Self>, BuildingError> {
if server.game() != Game::TeamFortress2 {
return Err(BuildingError::WrongGame);
}
let tools = server.server_tools()?;
Ok(CLASSES
.into_iter()
.flat_map(move |class| {
std::iter::successors(tools.find_by_class_name(None, class), move |&entity| {
tools.find_by_class_name(Some(entity), class)
})
})
.filter(|entity| !entity.is_marked_for_deletion())
.filter_map(move |entity| Self::new(server, entity).ok()))
}
fn bool(self, name: &CStr) -> Result<bool, BuildingError> {
self.get(name)
}
#[doc(alias("m_hBuilder", "GetBuilder"))]
pub fn builder(self) -> Result<Option<Entity<'s>>, BuildingError> {
self.handle(c"m_hBuilder")
}
#[doc(alias("m_hBuiltOnEntity", "GetParentObject"))]
pub fn built_on(self) -> Result<Option<Entity<'s>>, BuildingError> {
self.handle(c"m_hBuiltOnEntity")
}
fn check_removable(self) -> Result<(), BuildingError> {
if self.entity.is_marked_for_deletion() {
return Err(BuildingError::MarkedForDeletion);
}
if self.is_dying() {
return Err(BuildingError::Dying);
}
if self.is_placing()? {
return Err(BuildingError::Blueprint);
}
Ok(())
}
#[doc(alias("m_flPercentageConstructed", "GetPercentageConstructed"))]
pub fn construction_progress(self) -> Result<f32, BuildingError> {
self.get(c"m_flPercentageConstructed")
}
#[doc(alias("DestroyObject"))]
pub fn destroy(self) -> Result<(), BuildingError> {
self.check_removable()?;
let building = self.entity.as_ptr();
unsafe {
vcall!(building as sys::CBaseObject__bindgen_vtable => CBaseObject_DestroyObject())
};
Ok(())
}
#[doc(alias("DetonateObject"))]
pub fn detonate(self) -> Result<(), BuildingError> {
self.check_removable()?;
let building = self.entity.as_ptr();
unsafe {
vcall!(building as sys::CBaseObject__bindgen_vtable => CBaseObject_DetonateObject())
};
Ok(())
}
#[doc(alias("Disable", "InputDisable"))]
pub fn disable(self) -> Result<(), BuildingError> {
self.input(c"Disable", InputValue::Void)
}
#[doc(alias("Enable", "InputEnable"))]
pub fn enable(self) -> Result<(), BuildingError> {
self.input(c"Enable", InputValue::Void)
}
pub const fn entity(self) -> Entity<'s> {
self.entity
}
#[doc(alias("m_fObjectFlags", "GetObjectFlags"))]
pub fn flags(self) -> Result<ObjectFlags, BuildingError> {
Ok(ObjectFlags::from_bits_retain(self.int(c"m_fObjectFlags")?))
}
fn get<T: NetVar>(self, name: &CStr) -> Result<T, BuildingError> {
Ok(self.net_prop(name)?.get(self.entity)?)
}
fn handle(self, name: &CStr) -> Result<Option<Entity<'s>>, BuildingError> {
let handle = self.net_prop(name)?.get_handle(self.entity)?;
Ok(self.server.server_tools()?.entity_by_handle(handle))
}
#[doc(alias("Hide", "InputHide"))]
pub fn hide(self) -> Result<(), BuildingError> {
self.input(c"Hide", InputValue::Void)
}
#[doc(alias("m_iHighestUpgradeLevel", "GetHighestUpgradeLevel"))]
pub fn highest_level(self) -> Result<c_int, BuildingError> {
self.int(c"m_iHighestUpgradeLevel")
}
fn input(self, name: &CStr, value: InputValue<'_>) -> Result<(), BuildingError> {
Ok(self.server.server_tools()?.accept_input(
self.entity,
name,
value,
self.entity,
self.entity,
)?)
}
fn int(self, name: &CStr) -> Result<c_int, BuildingError> {
self.get(name)
}
#[doc(alias("m_bCarried", "IsCarried"))]
pub fn is_carried(self) -> Result<bool, BuildingError> {
self.bool(c"m_bCarried")
}
#[doc(alias("m_bBuilding", "IsBuilding"))]
pub fn is_constructing(self) -> Result<bool, BuildingError> {
self.bool(c"m_bBuilding")
}
#[doc(alias("m_bDisabled", "IsDisabled"))]
pub fn is_disabled(self) -> Result<bool, BuildingError> {
self.bool(c"m_bDisabled")
}
#[doc(alias("m_bDisposableBuilding", "IsDisposableBuilding"))]
pub fn is_disposable(self) -> Result<bool, BuildingError> {
self.bool(c"m_bDisposableBuilding")
}
#[doc(alias("IsDying", "m_bDying"))]
pub fn is_dying(self) -> bool {
let building = self.entity.as_ptr();
unsafe { vcall!(building as sys::CBaseObject__bindgen_vtable => CBaseObject_IsDying()) }
}
#[doc(alias("m_bMiniBuilding", "IsMiniBuilding"))]
pub fn is_mini(self) -> Result<bool, BuildingError> {
self.bool(c"m_bMiniBuilding")
}
#[doc(alias("m_bPlacing", "IsPlacing"))]
pub fn is_placing(self) -> Result<bool, BuildingError> {
self.bool(c"m_bPlacing")
}
#[doc(alias("m_bPlasmaDisable", "IsPlasmaDisabled"))]
pub fn is_plasma_disabled(self) -> Result<bool, BuildingError> {
self.bool(c"m_bPlasmaDisable")
}
#[doc(alias("m_bCarryDeploy", "IsRedeploying"))]
pub fn is_redeploying(self) -> Result<bool, BuildingError> {
self.bool(c"m_bCarryDeploy")
}
#[doc(alias("m_bHasSapper", "HasSapper"))]
pub fn is_sapped(self) -> Result<bool, BuildingError> {
self.bool(c"m_bHasSapper")
}
#[doc(alias("m_iObjectType", "GetType", "ObjectType"))]
pub fn kind(self) -> Result<BuildingKind, BuildingError> {
let raw = self.int(c"m_iObjectType")?;
BuildingKind::from_raw(raw).ok_or(BuildingError::UnknownKind(raw))
}
#[doc(alias("m_iUpgradeLevel", "GetUpgradeLevel"))]
pub fn level(self) -> Result<c_int, BuildingError> {
self.int(c"m_iUpgradeLevel")
}
#[doc(alias("GetMaxUpgradeLevel"))]
pub fn max_level(self) -> c_int {
let building = self.entity.as_ptr();
unsafe {
vcall!(building as sys::CBaseObject__bindgen_vtable => CBaseObject_GetMaxUpgradeLevel())
}
}
#[doc(alias("m_iObjectMode", "GetObjectMode"))]
pub fn mode(self) -> Result<c_int, BuildingError> {
self.int(c"m_iObjectMode")
}
fn net_prop(self, name: &CStr) -> Result<NetProp<'s>, BuildingError> {
Ok(self
.server
.server_game_dll()?
.entity_net_prop(self.entity, name)?)
}
#[doc(alias("GetSapper", "GetObjectOfTypeOnMe"))]
pub fn sapper(self) -> Result<Option<Self>, BuildingError> {
let tools = self.server.server_tools()?;
let handle = self.entity.handle();
let mut found = tools.find_by_class_name(None, SAPPER_CLASS);
while let Some(entity) = found {
if !entity.is_marked_for_deletion()
&& let Ok(sapper) = Self::new(self.server, entity)
&& sapper.net_prop(c"m_hBuiltOnEntity")?.get_handle(entity)? == handle
{
return Ok(Some(sapper));
}
found = tools.find_by_class_name(Some(entity), SAPPER_CLASS);
}
Ok(None)
}
#[doc(alias("SetBuilder", "InputSetBuilder", "AddObject"))]
pub fn set_builder(self, player: Entity<'s>) -> Result<(), BuildingError> {
if !player.has_data_map_class(c"CTFPlayer") {
return Err(BuildingError::NotTfPlayer);
}
if self.entity.is_marked_for_deletion() || player.is_marked_for_deletion() {
return Err(BuildingError::MarkedForDeletion);
}
if self.is_dying() {
return Err(BuildingError::Dying);
}
if self.builder()?.is_some() {
return Err(BuildingError::HasBuilder);
}
Ok(self.server.server_tools()?.accept_input(
self.entity,
c"SetBuilder",
InputValue::Void,
player,
self.entity,
)?)
}
#[doc(alias("SetSolidToPlayer", "InputSetSolidToPlayer", "SetSolidToPlayers"))]
pub fn set_solid_to_players(self, solid: SolidToPlayers) -> Result<(), BuildingError> {
self.input(c"SetSolidToPlayer", InputValue::Int(solid.to_raw()))
}
#[doc(alias("Show", "InputShow"))]
pub fn show(self) -> Result<(), BuildingError> {
self.input(c"Show", InputValue::Void)
}
#[doc(alias("StartPlacement", "StartBuilding"))]
unsafe fn start_construction(self, builder: Entity<'s>) -> bool {
let building = self.entity.as_ptr();
unsafe {
vcall!(building as sys::CBaseObject__bindgen_vtable => CBaseObject_StartPlacement(builder.as_ptr().cast()));
vcall!(building as sys::CBaseObject__bindgen_vtable => CBaseObject_StartBuilding(std::ptr::null_mut()))
}
}
#[doc(alias("m_iTeamNum", "GetTeamNumber"))]
pub fn team(self) -> Result<c_int, BuildingError> {
self.int(c"m_iTeamNum")
}
#[doc(alias("m_iUpgradeMetal"))]
pub fn upgrade_metal(self) -> Result<c_int, BuildingError> {
self.int(c"m_iUpgradeMetal")
}
#[doc(alias("m_iUpgradeMetalRequired", "GetUpgradeMetalRequired"))]
pub fn upgrade_metal_required(self) -> Result<c_int, BuildingError> {
self.int(c"m_iUpgradeMetalRequired")
}
#[doc(alias("m_bWasMapPlaced"))]
pub fn was_map_placed(self) -> Result<bool, BuildingError> {
self.bool(c"m_bWasMapPlaced")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuildingClass {
#[doc(alias("CObjectCartDispenser", "mapobj_cart_dispenser"))]
CartDispenser,
#[doc(alias("CObjectDispenser", "obj_dispenser"))]
Dispenser,
#[doc(alias("CPlayerDestructionDispenser", "pd_dispenser"))]
PlayerDestructionDispenser,
#[doc(alias("CRobotDispenser", "rd_robot_dispenser"))]
RobotDispenser,
#[doc(alias("CObjectSapper", "obj_attachment_sapper"))]
Sapper,
#[doc(alias("CObjectSentrygun", "obj_sentrygun"))]
Sentry,
#[doc(alias("CObjectTeleporter", "obj_teleporter"))]
Teleporter,
}
impl BuildingClass {
pub const ALL: [Self; 7] = [
Self::CartDispenser,
Self::Dispenser,
Self::PlayerDestructionDispenser,
Self::RobotDispenser,
Self::Sapper,
Self::Sentry,
Self::Teleporter,
];
pub const fn kind(self) -> BuildingKind {
match self {
Self::CartDispenser
| Self::Dispenser
| Self::PlayerDestructionDispenser
| Self::RobotDispenser => BuildingKind::Dispenser,
Self::Sapper => BuildingKind::Sapper,
Self::Sentry => BuildingKind::Sentry,
Self::Teleporter => BuildingKind::Teleporter,
}
}
pub const fn name(self) -> &'static str {
match self {
Self::CartDispenser => "CObjectCartDispenser",
Self::Dispenser => "CObjectDispenser",
Self::PlayerDestructionDispenser => "CPlayerDestructionDispenser",
Self::RobotDispenser => "CRobotDispenser",
Self::Sapper => "CObjectSapper",
Self::Sentry => "CObjectSentrygun",
Self::Teleporter => "CObjectTeleporter",
}
}
}
#[doc(alias("obj_sentrygun", "obj_dispenser", "obj_teleporter"))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuildingSpawn {
flags: c_int,
kind: ObjectKind,
level: Option<u8>,
spawn: EntitySpawn,
}
impl BuildingSpawn {
pub fn new(kind: ObjectKind) -> Self {
let class = match kind {
ObjectKind::Dispenser => c"obj_dispenser",
ObjectKind::Sentry => c"obj_sentrygun",
ObjectKind::Teleporter => c"obj_teleporter",
};
Self {
flags: 0,
kind,
level: None,
spawn: EntitySpawn::new(class),
}
}
pub fn angles(self, angles: QAngle) -> Self {
Self {
spawn: self.spawn.angles(angles),
..self
}
}
#[doc(alias("StartPlacement", "StartBuilding", "CTFWeaponBuilder"))]
pub unsafe fn build<'s>(
&self,
server: Server<'s>,
builder: Entity<'s>,
) -> Result<Building<'s>, BuildingError> {
if server.game() != Game::TeamFortress2 {
return Err(BuildingError::WrongGame);
}
if !builder.has_data_map_class(c"CTFPlayer") {
return Err(BuildingError::NotTfPlayer);
}
if builder.is_marked_for_deletion() {
return Err(BuildingError::MarkedForDeletion);
}
let spawn = self.entity_spawn()?.activate(false);
let tools = server.server_tools()?;
let entity = unsafe { spawn.spawn(tools) }?;
let given = Building::new(server, entity)
.and_then(|building| building.set_builder(builder).map(|()| building));
let building = match given {
Ok(building) => building,
Err(error) => {
let _ = tools.remove(entity);
return Err(error);
}
};
let started = unsafe { building.start_construction(builder) };
if !started || entity.is_marked_for_deletion() {
return Err(BuildingError::Spawn(SpawnError::RemovedItself));
}
Ok(building)
}
fn entity_spawn(&self) -> Result<EntitySpawn, BuildingError> {
let mut spawn = self.spawn.clone();
if let Some(level) = self.level {
if !(1..=3).contains(&level) {
return Err(BuildingError::InvalidLevel(level));
}
spawn = spawn.key(c"defaultupgrade", number(c_int::from(level) - 1).as_c_str());
}
if self.flags != 0 {
spawn = spawn.key(c"spawnflags", number(self.flags).as_c_str());
}
Ok(spawn)
}
fn flag(self, flag: c_int, set: bool) -> Self {
Self {
flags: if set {
self.flags | flag
} else {
self.flags & !flag
},
..self
}
}
#[doc(alias("SF_SENTRY_INFINITE_AMMO"))]
pub fn infinite_ammo(self, infinite: bool) -> Self {
if self.kind == ObjectKind::Sentry {
self.flag(raw::SF_SENTRY_INFINITE_AMMO, infinite)
} else {
self
}
}
#[doc(alias("SF_BASEOBJ_INVULN"))]
pub fn invulnerable(self, invulnerable: bool) -> Self {
self.flag(raw::SF_BASEOBJ_INVULN, invulnerable)
}
pub const fn kind(&self) -> ObjectKind {
self.kind
}
#[doc(alias("defaultupgrade", "m_nDefaultUpgradeLevel"))]
pub fn level(self, level: u8) -> Self {
Self {
level: Some(level),
..self
}
}
pub fn name(self, name: &CStr) -> Self {
Self {
spawn: self.spawn.name(name),
..self
}
}
pub fn origin(self, origin: Vector) -> Self {
Self {
spawn: self.spawn.origin(origin),
..self
}
}
#[doc(alias("SolidToPlayer"))]
pub fn solid_to_players(self, solid: SolidToPlayers) -> Self {
Self {
spawn: self
.spawn
.key(c"SolidToPlayer", number(solid.to_raw()).as_c_str()),
..self
}
}
pub unsafe fn spawn<'s>(&self, server: Server<'s>) -> Result<Building<'s>, BuildingError> {
if server.game() != Game::TeamFortress2 {
return Err(BuildingError::WrongGame);
}
let spawn = self.entity_spawn()?;
let entity = unsafe { spawn.spawn(server.server_tools()?) }?;
Building::new(server, entity)
}
#[doc(alias("TeamNum"))]
pub fn team(self, team: Team) -> Self {
Self {
spawn: self.spawn.key(c"TeamNum", number(team.to_raw()).as_c_str()),
..self
}
}
#[doc(alias("teleporterType", "TTYPE_ENTRANCE", "TTYPE_EXIT"))]
pub fn teleporter_end(self, end: TeleporterEnd) -> Self {
if self.kind != ObjectKind::Teleporter {
return self;
}
let value = match end {
TeleporterEnd::Entrance => raw::TTYPE_ENTRANCE,
TeleporterEnd::Exit => raw::TTYPE_EXIT,
};
Self {
spawn: self.spawn.key(c"teleporterType", number(value).as_c_str()),
..self
}
}
#[doc(alias("SF_SENTRY_UPGRADEABLE"))]
pub fn upgradable(self, upgradable: bool) -> Self {
if self.kind == ObjectKind::Sentry {
self.flag(raw::SF_SENTRY_UPGRADEABLE, upgradable)
} else {
self
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum BuildingVtableError {
#[error("the game module could not be inspected")]
Image(#[from] std::io::Error),
#[error("the game module has an unsupported executable image")]
InvalidImage,
#[error("no unique primary vtable found for TF2's building class `{}`", .0.name())]
NotFound(BuildingClass),
#[error("building hooks require Team Fortress 2")]
WrongGame,
}
impl From<util::Error> for BuildingVtableError {
fn from(error: util::Error) -> Self {
match error {
util::Error::InvalidImage => Self::InvalidImage,
util::Error::Io(error) => Self::Image(error),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct BuildingVtables<'s> {
vtables: [NonNull<*mut c_void>; 7],
_scope: PhantomData<&'s Server<'s>>,
}
impl<'s> BuildingVtables<'s> {
pub const fn all(self) -> [NonNull<*mut c_void>; 7] {
self.vtables
}
pub fn find(targets: &ClassTargets<'s>) -> Result<Self, BuildingVtableError> {
let objects = targets.objects().map_err(vtable_error)?;
Ok(Self {
vtables: objects.map(ClassTarget::as_ptr),
_scope: PhantomData,
})
}
pub const fn get(self, class: BuildingClass) -> NonNull<*mut c_void> {
self.vtables[class as usize]
}
}
#[doc(alias("CObjectDispenser"))]
#[derive(Debug, Clone, Copy)]
pub struct Dispenser<'s>(Building<'s>);
impl<'s> Dispenser<'s> {
pub fn new(building: Building<'s>) -> Result<Self, BuildingError> {
if !building.entity.has_data_map_class(c"CObjectDispenser") {
return Err(BuildingError::NotKind(BuildingKind::Dispenser));
}
Ok(Self(building))
}
pub const fn building(self) -> Building<'s> {
self.0
}
#[doc(alias("CObjectCartDispenser", "mapobj_cart_dispenser"))]
pub fn is_cart(self) -> bool {
self.0.entity.has_data_map_class(c"CObjectCartDispenser")
}
#[doc(alias("m_iAmmoMetal"))]
pub fn metal(self) -> Result<c_int, BuildingError> {
self.0.int(c"m_iAmmoMetal")
}
#[doc(alias("m_iState"))]
pub fn state(self) -> Result<DispenserState, BuildingError> {
let raw = self.0.int(c"m_iState")?;
DispenserState::from_raw(raw).ok_or(BuildingError::UnknownState(raw))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DispenserState {
#[doc(alias("DISPENSER_STATE_IDLE"))]
Idle,
#[doc(alias("DISPENSER_STATE_UPGRADING"))]
Upgrading,
}
impl DispenserState {
pub const fn from_raw(raw: c_int) -> Option<Self> {
Some(match raw {
raw::DISPENSER_STATE_IDLE => Self::Idle,
raw::DISPENSER_STATE_UPGRADING => Self::Upgrading,
_ => return None,
})
}
pub const fn to_raw(self) -> c_int {
match self {
Self::Idle => raw::DISPENSER_STATE_IDLE,
Self::Upgrading => raw::DISPENSER_STATE_UPGRADING,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PlayerBuildings<'s> {
server: Server<'s>,
player: Entity<'s>,
}
impl<'s> PlayerBuildings<'s> {
pub fn new(server: Server<'s>, player: Entity<'s>) -> Result<Self, BuildingError> {
if server.game() != Game::TeamFortress2 {
return Err(BuildingError::WrongGame);
}
if !player.has_data_map_class(c"CTFPlayer") {
return Err(BuildingError::NotTfPlayer);
}
Ok(Self { server, player })
}
pub fn all(self) -> Result<impl Iterator<Item = Building<'s>>, BuildingError> {
let player = self.player.handle();
Ok(Building::all(self.server)?.filter(move |building| {
building
.net_prop(c"m_hBuilder")
.ok()
.and_then(|builder| builder.get_handle(building.entity).ok())
== Some(player)
}))
}
unsafe fn call(
self,
method: &CStr,
arguments: &mut [sys::ScriptVariant_t],
result: sys::ScriptDataType_t,
) -> Result<sys::ScriptVariant_t, BuildingError> {
if self.player.is_marked_for_deletion() {
return Err(BuildingError::MarkedForDeletion);
}
Ok(unsafe { binding::call(self.player, c"CTFPlayer", method, arguments, result) }?)
}
#[doc(alias("m_hCarriedObject", "GetCarriedObject", "m_bCarryingObject"))]
pub fn carried(self) -> Result<Option<Building<'s>>, BuildingError> {
let handle = self
.server
.server_game_dll()?
.entity_net_prop(self.player, c"m_hCarriedObject")?
.get_handle(self.player)?;
let Some(entity) = self.server.server_tools()?.entity_by_handle(handle) else {
return Ok(None);
};
Building::new(self.server, entity).map(Some)
}
#[doc(alias("IsPlacingSapper"))]
pub fn is_placing_sapper(self) -> Result<bool, BuildingError> {
let result = unsafe { self.call(c"IsPlacingSapper", &mut [], BOOL) }?;
Ok(unsafe { result.__bindgen_anon_1.m_bool })
}
#[doc(alias("IsSapping"))]
pub fn is_sapping(self) -> Result<bool, BuildingError> {
let result = unsafe { self.call(c"IsSapping", &mut [], BOOL) }?;
Ok(unsafe { result.__bindgen_anon_1.m_bool })
}
#[doc(alias("TryToPickupBuilding"))]
pub fn pick_up(self) -> Result<bool, BuildingError> {
let result = unsafe { self.call(c"TryToPickupBuilding", &mut [], BOOL) }?;
Ok(unsafe { result.__bindgen_anon_1.m_bool })
}
pub const fn player(self) -> Entity<'s> {
self.player
}
#[doc(alias("RemoveAllObjects"))]
pub fn remove_all(self, explode: bool) -> Result<(), BuildingError> {
unsafe { self.call(c"RemoveAllObjects", &mut [boolean(explode)], binding::VOID) }?;
Ok(())
}
}
#[doc(alias("CObjectSentrygun"))]
#[derive(Debug, Clone, Copy)]
pub struct Sentry<'s>(Building<'s>);
impl<'s> Sentry<'s> {
pub fn new(building: Building<'s>) -> Result<Self, BuildingError> {
if !building.entity.has_data_map_class(c"CObjectSentrygun") {
return Err(BuildingError::NotKind(BuildingKind::Sentry));
}
Ok(Self(building))
}
#[doc(alias("m_iAssists"))]
pub fn assists(self) -> Result<c_int, BuildingError> {
self.0.int(c"m_iAssists")
}
pub const fn building(self) -> Building<'s> {
self.0
}
#[doc(alias("m_hEnemy", "GetEnemy"))]
pub fn enemy(self) -> Result<Option<Entity<'s>>, BuildingError> {
self.0.handle(c"m_hEnemy")
}
#[doc(alias("m_bPlayerControlled", "IsPlayerControlled"))]
pub fn is_wrangled(self) -> Result<bool, BuildingError> {
self.0.bool(c"m_bPlayerControlled")
}
#[doc(alias("m_iKills"))]
pub fn kills(self) -> Result<c_int, BuildingError> {
self.0.int(c"m_iKills")
}
#[doc(alias("m_iAmmoRockets"))]
pub fn rockets(self) -> Result<c_int, BuildingError> {
self.0.int(c"m_iAmmoRockets")
}
#[doc(alias("m_iAmmoShells"))]
pub fn shells(self) -> Result<c_int, BuildingError> {
self.0.int(c"m_iAmmoShells")
}
#[doc(alias("m_nShieldLevel", "GetShieldLevel"))]
pub fn shield_level(self) -> Result<c_int, BuildingError> {
self.0.int(c"m_nShieldLevel")
}
#[doc(alias("m_iState"))]
pub fn state(self) -> Result<SentryState, BuildingError> {
let raw = self.0.int(c"m_iState")?;
SentryState::from_raw(raw).ok_or(BuildingError::UnknownState(raw))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SentryState {
#[doc(alias("SENTRY_STATE_ATTACKING"))]
Attacking,
#[doc(alias("SENTRY_STATE_INACTIVE"))]
Inactive,
#[doc(alias("SENTRY_STATE_SEARCHING"))]
Searching,
#[doc(alias("SENTRY_STATE_UPGRADING"))]
Upgrading,
}
impl SentryState {
pub const fn from_raw(raw: c_int) -> Option<Self> {
Some(match raw {
raw::SENTRY_STATE_INACTIVE => Self::Inactive,
raw::SENTRY_STATE_SEARCHING => Self::Searching,
raw::SENTRY_STATE_ATTACKING => Self::Attacking,
raw::SENTRY_STATE_UPGRADING => Self::Upgrading,
_ => return None,
})
}
pub const fn to_raw(self) -> c_int {
match self {
Self::Inactive => raw::SENTRY_STATE_INACTIVE,
Self::Searching => raw::SENTRY_STATE_SEARCHING,
Self::Attacking => raw::SENTRY_STATE_ATTACKING,
Self::Upgrading => raw::SENTRY_STATE_UPGRADING,
}
}
}
#[doc(alias("OBJSOLIDTYPE", "SolidToPlayer"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SolidToPlayers {
#[doc(alias("SOLID_TO_PLAYER_USE_DEFAULT"))]
Default,
#[doc(alias("SOLID_TO_PLAYER_NO"))]
No,
#[doc(alias("SOLID_TO_PLAYER_YES"))]
Yes,
}
impl SolidToPlayers {
pub const fn to_raw(self) -> c_int {
match self {
Self::Default => raw::SOLID_TO_PLAYER_USE_DEFAULT,
Self::Yes => raw::SOLID_TO_PLAYER_YES,
Self::No => raw::SOLID_TO_PLAYER_NO,
}
}
}
#[doc(alias("CObjectTeleporter"))]
#[derive(Debug, Clone, Copy)]
pub struct Teleporter<'s>(Building<'s>);
impl<'s> Teleporter<'s> {
pub fn new(building: Building<'s>) -> Result<Self, BuildingError> {
if !building.entity.has_data_map_class(c"CObjectTeleporter") {
return Err(BuildingError::NotKind(BuildingKind::Teleporter));
}
Ok(Self(building))
}
pub const fn building(self) -> Building<'s> {
self.0
}
#[doc(alias("m_iObjectMode", "IsEntrance", "IsExit"))]
pub fn end(self) -> Result<TeleporterEnd, BuildingError> {
Ok(match self.0.mode()? {
raw::MODE_TELEPORTER_ENTRANCE => TeleporterEnd::Entrance,
_ => TeleporterEnd::Exit,
})
}
#[doc(alias("m_flCurrentRechargeDuration"))]
pub fn recharge_duration(self) -> Result<f32, BuildingError> {
self.0.get(c"m_flCurrentRechargeDuration")
}
#[doc(alias("m_flRechargeTime"))]
pub fn recharge_time(self) -> Result<f32, BuildingError> {
self.0.get(c"m_flRechargeTime")
}
#[doc(alias("m_iState"))]
pub fn state(self) -> Result<TeleporterState, BuildingError> {
let raw = self.0.int(c"m_iState")?;
TeleporterState::from_raw(raw).ok_or(BuildingError::UnknownState(raw))
}
#[doc(alias("m_iTimesUsed"))]
pub fn times_used(self) -> Result<c_int, BuildingError> {
self.0.int(c"m_iTimesUsed")
}
#[doc(alias("m_flYawToExit"))]
pub fn yaw_to_exit(self) -> Result<f32, BuildingError> {
self.0.get(c"m_flYawToExit")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TeleporterEnd {
#[doc(alias("MODE_TELEPORTER_ENTRANCE", "TTYPE_ENTRANCE"))]
Entrance,
#[doc(alias("MODE_TELEPORTER_EXIT", "TTYPE_EXIT"))]
Exit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TeleporterState {
#[doc(alias("TELEPORTER_STATE_BUILDING"))]
Building,
#[doc(alias("TELEPORTER_STATE_IDLE"))]
Idle,
#[doc(alias("TELEPORTER_STATE_READY"))]
Ready,
#[doc(alias("TELEPORTER_STATE_RECEIVING"))]
Receiving,
#[doc(alias("TELEPORTER_STATE_RECEIVING_RELEASE"))]
ReceivingRelease,
#[doc(alias("TELEPORTER_STATE_RECHARGING"))]
Recharging,
#[doc(alias("TELEPORTER_STATE_SENDING"))]
Sending,
#[doc(alias("TELEPORTER_STATE_UPGRADING"))]
Upgrading,
}
impl TeleporterState {
pub const fn from_raw(raw: c_int) -> Option<Self> {
Some(match raw {
raw::TELEPORTER_STATE_BUILDING => Self::Building,
raw::TELEPORTER_STATE_IDLE => Self::Idle,
raw::TELEPORTER_STATE_READY => Self::Ready,
raw::TELEPORTER_STATE_SENDING => Self::Sending,
raw::TELEPORTER_STATE_RECEIVING => Self::Receiving,
raw::TELEPORTER_STATE_RECEIVING_RELEASE => Self::ReceivingRelease,
raw::TELEPORTER_STATE_RECHARGING => Self::Recharging,
raw::TELEPORTER_STATE_UPGRADING => Self::Upgrading,
_ => return None,
})
}
pub const fn to_raw(self) -> c_int {
match self {
Self::Building => raw::TELEPORTER_STATE_BUILDING,
Self::Idle => raw::TELEPORTER_STATE_IDLE,
Self::Ready => raw::TELEPORTER_STATE_READY,
Self::Sending => raw::TELEPORTER_STATE_SENDING,
Self::Receiving => raw::TELEPORTER_STATE_RECEIVING,
Self::ReceivingRelease => raw::TELEPORTER_STATE_RECEIVING_RELEASE,
Self::Recharging => raw::TELEPORTER_STATE_RECHARGING,
Self::Upgrading => raw::TELEPORTER_STATE_UPGRADING,
}
}
}
pub fn building_vtables(server: Server<'_>) -> Result<BuildingVtables<'_>, BuildingVtableError> {
let targets = ClassTargets::load(server).map_err(vtable_error)?;
BuildingVtables::find(&targets)
}
fn number(number: c_int) -> CString {
CString::new(number.to_string()).expect("numbers have no NUL")
}
fn vtable_error(error: ClassTargetError) -> BuildingVtableError {
match error {
ClassTargetError::Image(error) => BuildingVtableError::Image(error),
ClassTargetError::InvalidImage => BuildingVtableError::InvalidImage,
ClassTargetError::NotFound(name) => BuildingVtableError::NotFound(
BuildingClass::ALL
.into_iter()
.find(|class| class.name() == name)
.expect("the SDK's object classes are the building classes"),
),
ClassTargetError::WrongGame => BuildingVtableError::WrongGame,
}
}