#[cfg(test)]
#[path = "../tests/tf2/dominations.rs"]
mod tests;
use crate::datatables::{NetProp, NetPropError};
use crate::entities::Entity;
use crate::{Game, InterfaceError, Server};
use sdk_raw::tf2::scoreboard::MAX_PLAYERS_ARRAY_SAFE;
use sdk_raw::vcall;
use std::ffi::{CStr, c_int};
const DOMINATED: &CStr = c"m_bPlayerDominated";
const DOMINATING_ME: &CStr = c"m_bPlayerDominatingMe";
#[derive(Debug, thiserror::Error)]
pub enum DominationError {
#[error(transparent)]
Interface(#[from] InterfaceError),
#[error(transparent)]
NetProp(#[from] NetPropError),
#[error("dominations require a TF2 player")]
NotTfPlayer,
}
#[derive(Debug, Clone, Copy)]
pub struct PlayerDominations<'s> {
index: usize,
player: Entity<'s>,
server: Server<'s>,
}
impl<'s> PlayerDominations<'s> {
pub fn new(server: Server<'s>, player: Entity<'s>) -> Result<Self, DominationError> {
if server.game() != Game::TeamFortress2 || !player.has_data_map_class(c"CTFPlayer") {
return Err(DominationError::NotTfPlayer);
}
let index = player
.index()
.filter(|index| (1..MAX_PLAYERS_ARRAY_SAFE).contains(index))
.ok_or(DominationError::NotTfPlayer)?;
Ok(Self {
index,
player,
server,
})
}
#[doc(alias("GetNumberofDominations", "m_iNumberofDominations"))]
pub fn count(self) -> c_int {
let player = self.player.as_ptr().cast::<sys::CTFPlayer>();
unsafe {
vcall!(player as sys::CTFPlayer__bindgen_vtable => CTFPlayer_GetNumberofDominations())
}
}
#[doc(alias("IsPlayerDominated", "m_bPlayerDominated"))]
pub fn dominates(self, other: Self) -> Result<bool, DominationError> {
self.flag(self.net_prop(DOMINATED)?, other.index)
}
pub fn end(self, victim: Self) -> Result<bool, DominationError> {
let dominated = self.dominates(victim)?;
self.set_flag(self.net_prop(DOMINATED)?, victim.index, false)?;
victim.set_flag(victim.net_prop(DOMINATING_ME)?, self.index, false)?;
if dominated {
self.set_count(self.count().saturating_sub(1));
}
Ok(dominated)
}
#[doc(alias("RemoveNemesisRelationships"))]
pub fn end_all(self) -> Result<usize, DominationError> {
let tools = self.server.server_tools()?;
let dominated = self.net_prop(DOMINATED)?;
let dominating_me = self.net_prop(DOMINATING_ME)?;
let len = [dominated, dominating_me]
.into_iter()
.filter_map(NetProp::element_count)
.fold(MAX_PLAYERS_ARRAY_SAFE, usize::min);
let other_at = |index: usize| {
let entity = tools.entity_by_index(c_int::try_from(index).ok()?)?;
Self::new(self.server, entity)
.ok()
.filter(|other| other.index == index)
};
let mut ended = 0;
for index in (1..len).filter(|&index| index != self.index) {
if self.flag(dominated, index)? {
self.set_flag(dominated, index, false)?;
ended += 1;
if let Some(other) = other_at(index) {
other.set_flag(other.net_prop(DOMINATING_ME)?, self.index, false)?;
}
}
if self.flag(dominating_me, index)? {
self.set_flag(dominating_me, index, false)?;
ended += 1;
if let Some(other) = other_at(index) {
other.set_flag(other.net_prop(DOMINATED)?, self.index, false)?;
other.set_count(other.count().saturating_sub(1));
}
}
}
if self.count() != 0 {
self.set_count(0);
}
Ok(ended)
}
fn flag(self, array: NetProp<'_>, index: usize) -> Result<bool, DominationError> {
Ok(array.element(index)?.get::<bool>(self.player)?)
}
#[doc(alias("IsPlayerDominatingMe", "m_bPlayerDominatingMe"))]
pub fn is_dominated_by(self, other: Self) -> Result<bool, DominationError> {
self.flag(self.net_prop(DOMINATING_ME)?, other.index)
}
fn net_prop(self, name: &CStr) -> Result<NetProp<'s>, DominationError> {
Ok(self
.server
.server_game_dll()?
.entity_net_prop(self.player, name)?)
}
pub const fn player(self) -> Entity<'s> {
self.player
}
fn set_count(self, count: c_int) {
let player = self.player.as_ptr().cast::<sys::CTFPlayer>();
unsafe {
vcall!(player as sys::CTFPlayer__bindgen_vtable => CTFPlayer_SetNumberofDominations(count.max(0)))
};
}
fn set_flag(
self,
array: NetProp<'_>,
index: usize,
value: bool,
) -> Result<(), DominationError> {
let element = array.element(index)?;
if element.get::<bool>(self.player)? != value {
unsafe { element.set(self.server.valve_engine()?, self.player, value) }?;
}
Ok(())
}
}