#[cfg(test)]
#[path = "../tests/tf2/meters.rs"]
mod tests;
use crate::datatables::{NetProp, NetVar};
use crate::entities::{Entity, EntityHandle};
use crate::tf2::effects::EffectError;
use crate::tf2::player_methods;
use crate::{Game, Server};
use std::ffi::{CStr, c_int};
const DECAPITATIONS_MAX: c_int = 255;
const METER_MAX: f32 = 100.0;
#[derive(Debug, Clone, Copy)]
pub struct Medigun<'s> {
server: Server<'s>,
weapon: Entity<'s>,
}
impl<'s> Medigun<'s> {
pub fn new(server: Server<'s>, weapon: Entity<'s>) -> Result<Self, EffectError> {
if server.game() != Game::TeamFortress2 || weapon.class_name() != c"tf_weapon_medigun" {
return Err(EffectError::NotTfPlayer);
}
Ok(Self { server, weapon })
}
#[doc(alias("m_flChargeLevel", "GetChargeLevel"))]
pub fn charge(self) -> Result<f32, EffectError> {
self.get(c"m_flChargeLevel")
}
fn get<T: NetVar>(self, name: &CStr) -> Result<T, EffectError> {
Ok(self.net_prop(name)?.get::<T>(self.weapon)?)
}
fn get_handle(self, name: &CStr) -> Result<Option<EntityHandle>, EffectError> {
let handle = self.net_prop(name)?.get_handle(self.weapon)?;
Ok(handle.is_valid().then_some(handle))
}
#[doc(alias("m_hHealingTarget", "GetHealTarget"))]
pub fn healing_target(self) -> Result<Option<EntityHandle>, EffectError> {
self.get_handle(c"m_hHealingTarget")
}
#[doc(alias("m_bAttacking"))]
pub fn is_attacking(self) -> Result<bool, EffectError> {
self.get(c"m_bAttacking")
}
#[doc(alias("m_bHealing"))]
pub fn is_healing(self) -> Result<bool, EffectError> {
self.get(c"m_bHealing")
}
#[doc(alias("m_bHolstered"))]
pub fn is_holstered(self) -> Result<bool, EffectError> {
self.get(c"m_bHolstered")
}
#[doc(alias("m_bChargeRelease"))]
pub fn is_releasing(self) -> Result<bool, EffectError> {
self.get(c"m_bChargeRelease")
}
#[doc(alias("m_hLastHealingTarget"))]
pub fn last_healing_target(self) -> Result<Option<EntityHandle>, EffectError> {
self.get_handle(c"m_hLastHealingTarget")
}
fn net_prop(self, name: &CStr) -> Result<NetProp<'s>, EffectError> {
Ok(self
.server
.server_game_dll()?
.entity_net_prop(self.weapon, name)?)
}
#[doc(alias("m_nChargeResistType", "GetResistType"))]
pub fn resistance(self) -> Result<Option<MedigunResistance>, EffectError> {
Ok(MedigunResistance::from_raw(
self.get(c"m_nChargeResistType")?,
))
}
#[doc(alias("SetChargeLevel"))]
pub fn set_charge(self, charge: f32) -> Result<(), EffectError> {
if !(0.0..=1.0).contains(&charge) {
return Err(EffectError::OutOfRange("charge"));
}
let engine = self.server.valve_engine()?;
let variable = self.net_prop(c"m_flChargeLevel")?;
Ok(unsafe { variable.set(engine, self.weapon, charge) }?)
}
pub const fn weapon(self) -> Entity<'s> {
self.weapon
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MedigunResistance {
#[doc(alias("MEDIGUN_BULLET_RESIST"))]
Bullet = 0,
#[doc(alias("MEDIGUN_BLAST_RESIST"))]
Blast = 1,
#[doc(alias("MEDIGUN_FIRE_RESIST"))]
Fire = 2,
}
impl MedigunResistance {
pub const fn from_raw(raw: c_int) -> Option<Self> {
match raw {
0 => Some(Self::Bullet),
1 => Some(Self::Blast),
2 => Some(Self::Fire),
_ => None,
}
}
pub const fn to_raw(self) -> c_int {
self as c_int
}
}
#[derive(Debug, Clone, Copy)]
pub struct PlayerMeters<'s> {
server: Server<'s>,
player: Entity<'s>,
}
impl<'s> PlayerMeters<'s> {
pub fn new(server: Server<'s>, player: Entity<'s>) -> Result<Self, EffectError> {
if !player_methods::is_tf_player(server, player) {
return Err(EffectError::NotTfPlayer);
}
Ok(Self { server, player })
}
#[doc(alias("m_flCloakMeter", "GetSpyCloakMeter"))]
pub fn cloak(self) -> Result<f32, EffectError> {
self.get(c"m_flCloakMeter")
}
#[doc(alias("m_iDecapitations", "GetDecapitations"))]
pub fn decapitations(self) -> Result<c_int, EffectError> {
self.get(c"m_iDecapitations")
}
#[doc(alias("m_flEnergyDrinkMeter", "GetScoutEnergyDrinkMeter"))]
pub fn energy_drink(self) -> Result<f32, EffectError> {
self.get(c"m_flEnergyDrinkMeter")
}
fn get<T: NetVar>(self, name: &CStr) -> Result<T, EffectError> {
Ok(self.net_prop(name)?.get::<T>(self.player)?)
}
#[doc(alias("m_nNumHealers", "GetNumHealers"))]
pub fn healers(self) -> Result<c_int, EffectError> {
self.get(c"m_nNumHealers")
}
#[doc(alias("m_flHypeMeter", "GetScoutHypeMeter"))]
pub fn hype(self) -> Result<f32, EffectError> {
self.get(c"m_flHypeMeter")
}
#[doc(alias("m_bRageDraining", "IsRageDraining"))]
pub fn is_rage_draining(self) -> Result<bool, EffectError> {
self.get(c"m_bRageDraining")
}
#[doc(alias("m_flItemChargeMeter", "GetItemChargeMeter"))]
pub fn item_charge(self, slot: usize) -> Result<f32, EffectError> {
Ok(self
.net_prop(c"m_flItemChargeMeter")?
.element(slot)?
.get::<f32>(self.player)?)
}
fn net_prop(self, name: &CStr) -> Result<NetProp<'s>, EffectError> {
Ok(self
.server
.server_game_dll()?
.entity_net_prop(self.player, name)?)
}
pub const fn player(self) -> Entity<'s> {
self.player
}
#[doc(alias("m_flRageMeter", "GetRageMeter"))]
pub fn rage(self) -> Result<f32, EffectError> {
self.get(c"m_flRageMeter")
}
#[doc(alias("m_iRevengeCrits", "GetRevengeCrits"))]
pub fn revenge_crits(self) -> Result<c_int, EffectError> {
self.get(c"m_iRevengeCrits")
}
fn set<T: NetVar>(self, name: &'static CStr, value: T, valid: bool) -> Result<(), EffectError> {
if !valid {
return Err(EffectError::OutOfRange("value"));
}
let engine = self.server.valve_engine()?;
let variable = self.net_prop(name)?;
Ok(unsafe { variable.set(engine, self.player, value) }?)
}
#[doc(alias("SetSpyCloakMeter"))]
pub fn set_cloak(self, cloak: f32) -> Result<(), EffectError> {
self.set(c"m_flCloakMeter", cloak, is_meter(cloak))
}
#[doc(alias("SetDecapitations"))]
pub fn set_decapitations(self, heads: c_int) -> Result<(), EffectError> {
self.set(
c"m_iDecapitations",
heads,
(0..=DECAPITATIONS_MAX).contains(&heads),
)
}
#[doc(alias("SetScoutEnergyDrinkMeter"))]
pub fn set_energy_drink(self, meter: f32) -> Result<(), EffectError> {
self.set(c"m_flEnergyDrinkMeter", meter, is_meter(meter))
}
#[doc(alias("SetScoutHypeMeter"))]
pub fn set_hype(self, hype: f32) -> Result<(), EffectError> {
self.set(c"m_flHypeMeter", hype, is_meter(hype))
}
pub fn set_item_charge(self, slot: usize, charge: f32) -> Result<(), EffectError> {
if !is_meter(charge) {
return Err(EffectError::OutOfRange("charge"));
}
let engine = self.server.valve_engine()?;
let variable = self.net_prop(c"m_flItemChargeMeter")?.element(slot)?;
Ok(unsafe { variable.set(engine, self.player, charge) }?)
}
#[doc(alias("SetRageMeter"))]
pub fn set_rage(self, rage: f32) -> Result<(), EffectError> {
self.set(c"m_flRageMeter", rage, is_meter(rage))
}
#[doc(alias("SetDemomanChargeMeter"))]
pub fn set_shield_charge(self, charge: f32) -> Result<(), EffectError> {
self.set(c"m_flChargeMeter", charge, is_meter(charge))
}
#[doc(alias("m_flChargeMeter", "GetDemomanChargeMeter"))]
pub fn shield_charge(self) -> Result<f32, EffectError> {
self.get(c"m_flChargeMeter")
}
}
fn is_meter(value: f32) -> bool {
(0.0..=METER_MAX).contains(&value)
}